AQS层层剖析

26 篇文章 0 订阅

AQS

B站视频:https://www.bilibili.com/video/BV12K411G7Fg

AQS(AbstractQueueSynchronizer) JUC经典同步框架

AQS的成员属性:

在这里插入图片描述

state就是用于判断共享资源是否正在被占用的标记位

volatile保证了线程之间的可见性

线程获取锁有两种模式:独占和共享

当一个线程以独占模式获取锁时,其他线程都必须等待

当一个线程以共享模式获取锁时,其他也想以共享模式获取锁的线程,也能够争抢锁,从而一起访问共享资源

在共享模式下,可能会有多个线程正在共享资源,所以state需要表示线程占用的数量,所以是int类型的

头结点和尾节点,它是一个FIFO先进先出的双向链表

如果一个线程在当前时刻没有获取到资源,它可以选择排队等待,或者直接放弃

head和tail则表示这个队列的头和尾

我们来看一下Node的具体结构:

在这里插入图片描述

node成员变量中存在着等待状态、线程对象、前后指针等信息

在这里插入图片描述

在源码中,tryAcquire方法是被protected修饰,参数是int值,代表队state的修改,

返回值boolean,代表是否成功返回锁

只抛出一个不支持操作的异常,很明显,这是需要开发者继承AQS类并且重写tryAcquire方法

在这里插入图片描述

重写之后,如果选择等待锁,那么可以直接调用Acquire方法

在这里插入图片描述

public final显然是在说所有的继承类都可以直接调用这个方法,而且不允许继承类override

这里的tryAcquire如果获得锁true,那么!tryAcquire为false ,那么将直接跳出判断条件,

不再执行后续的判断条件以及selfInterrupt方法

假如tryAcquire返回false,那么!tryAcquire为true,进而执行acquireQueued方法进行排队等待锁

里面有一个addWaiter方法

首先来看一下addWaiter方法

在这里插入图片描述

该方法的作用是将当前线程封装成一个node,然后加入等待队列,返回值为当前的节点

大致是创建一个新的Node,将新建的node插入到队尾

如果程序没有进入if路径,也就是说当前尾节点为空或者第一次尝试CAS操作失败,那么将会进入完整的入队方法

在这里插入图片描述

完整的入队方法中,有许多重复的代码,这是为什么呢?

Doug Lea大师可能经过大量测试后发现,大量的进行重复的判空操作可能会影响性能,

所以想将这一步enq中的if(t == null)省掉,追求极致的性能!

然后再来看acquireQueued方法
在这里插入图片描述

定义一个failed赋值为true,当程序正常return时failed都会被赋值为false,

只有当程序发生异常时,才会进入finally块中,执行cancelAcquire方法。

cancelAcquire(node)方法就是将node的waitState设置为CANCEL。

方法体中的意思是:如果当前线程自旋尝试获取锁成功,直接返回。

在AQS的FIFO队列中,头结点其实是一个虚节点,意思是说,头结点并不是当前需要去拿锁的节点,

它只是充当一个占位的摆设,而第二个节点才是真正需要去拿锁的节点,

当第二节点拿到锁之后,他就会变成头结点,然后头结点就会出队。

acquireQueued方法总的可以这么说:

当前线程是头结点的下一个节点,将不断地尝试拿锁,直到拿锁成功,

否则进行判断是否需要挂起。那如何判断是不是需要挂起呢?

判断条件:当前节点之前是不是除了head节点还有其他的节点,并且waitStates为SINGAL,

那么当前节点就需要被挂起。这样就能保证head节点之后只有一个节点在CAS尝试获取锁。

其他线程都被挂起或者正在被挂起,这样能最大限度的避免无用的自旋消耗CPU。

大量线程被挂起,什么时候被唤醒呢?

在这里插入图片描述

在release中,假如尝试获取锁成功,那么就要唤醒等待队列的其他节点。

主要看unparkSuccessor(h)方法,它的参数是head节点

unparkSuccessor

在这里插入图片描述

这个传进来的head节点就是获得了锁的节点,到了这里需要release的时候它的使命其实已经完成了

这个时候head只是作为一个占位的虚节点,所以需要将它的waitStates设置为默认值0,

才不会影响其他方法的一些判断,

然后程序将会从队列的尾节点开始搜索,找到除了head之外最靠前的节点,即head的后面一个(第二个)

并且waitStates小于等于0的节点,并对其进行LockSupport.unpark(s.thread)操作唤醒该挂起的线程,

之前挂起的线程一旦被唤醒,那么它将继续执行acquireQueued方法进行自旋尝试获得锁。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值