AQS源码分析
2022-09-16 11:56:05 2 举报
以ReentrantLock为例,分析 AQS 的基本原理
作者其他创作
大纲/内容
pred != null
acquireQueued方法详解
prev
将当前对象封装成Node对象,并加入排队队列中。根据排队队列是否初始化过,执行 1、2 不同的逻辑。1、表示排队队列不为空,即之前已经初始化过了,此时,只需要将新的 node 加入到排队队列末尾即可。2、排队队列为空,需要执行队列初始化。enq 会初始化一个空的 Node,作为排队队列的 head,然后将需要排队的线程作为 head 的 next 结点插入到队列
这部分是 node 加入队列后的处理逻辑,这里会有一次自旋,尝试获取锁,获取不到,才调用 park,阻塞自己
tryAquire()方法,尝试获取锁,以下几种情况会导致获取锁失败:1、锁已经被其他线程获取;2、锁没有被其他线程获取,但当前线程需要排队;3、CAS失败(可能过程中已经有其他线程获取到锁);锁为自由态(c == 0,并不能说明可以立即执行CAS获取锁,因为可能在当前线程获取锁之前,已经有线程在排队,必须遵循先来后到原则获取锁。所以还需要调用 hasQueuedPredecessors方法,查看自己是否需要排队。
表示当前线程未中断,则进行 acquireQueued 的下一轮的自旋
设置重入锁计数器
整个tryAcquire方法返回 true,表示获取锁成功
整个tryAcquire返回 false,表示获取锁失败
FairSync#lock()
表示获取到锁:1、node 设置为头结点;2、释放之前的头结点,这个逻辑执行完成后,node 结点就不再排队了(获取到锁的线程,不会在排队队列中了)
acquire(1)
head
是
非公平锁先通过CAS抢锁,抢不到再调用 acquire()
true
false
acquireQueued
没有初始化
p == head?表示查询 node 是不是第一个排队的
公平锁
已经初始化
队列尚未初始化,调用 enq 方法。该方法生成一个空的Node对象(new Node(),右图红色部分),插入到AQS队列头部,然后将参数 node 作为其后继结点,插入队列,完成后队列如右图所示
CAS获取锁
hasQueuedPecessors方法执行流程
abstract void lock()
tail
AQS 有两个属性 head 和 tail,分别用来AQS队列的头尾结点,初始时这两个属性都是 null,当有一个线程排队,队列如下
队列是否初始化?
tryAcquire
队列元素大于1,则至少为2,h.next 指向第二个元素,肯定不是 null,所以 (s = h.next)== null,返回 false
next
这个队列表示,在结点n3插入之前,已经有一个结点n2在排队获取锁了。
队列元素大于1
hasQueuedPredecessors() 方法用于获取锁之前,判断AQS队列中是否有其他线程在当前现在之前在排队。1、如果有,则返回 true,表示当前线程也需要排队。2、如果没有,则返回 false,表示当前线程无需排队
整个ReentrantLock的加锁过程可以分为三个阶段:1、尝试加锁2、加锁失败,线程入队列3、线程入队列后,线程进入阻塞状态对应下面①②③三部分
否
AQS队列(链表)
Node: n1thread = nullprev = nullnext = n2
再次循环尝试
parkAndCheckInterrupt方法详解
tryAcquire(1)
返回false,表示线程没有获取到锁
阻塞结束
返回 true,表示需要排队
设置 acquireQueued 方法中的 interrupted 标志位为 true,表示当前线程已中断。同时会清除当前线程的中断标志位
hasQueuedPredecssors()
返回node
NonfairSync#lock()
非公平锁
ReentrantLock
3
false,再次循环判断
队列没有初始化,则 h != t 返回 false,因为此时 h == t == null。整个 hasQueuedPredecessors 方法返回 false,表示不需要排队
sync继承了 AbstractQueuedSynchronizer
这部分逻辑,是尝试获取锁失败的情况下,当前线程(尝试获取锁的)封装成Node对象,加入到 AQS 同步队列中的逻辑。
enq方法
当最后一个排队线程也获取到了锁,此时 head == tail,且不等于 null,排队队列如左图
2
addWaiterx详解
s.thread != Thread.currentThread()
addWaiter
Node: n2thread = t2prev = n1next = n3
整个 hasQueuedPredecessors 返回 false,表示不需要排队
返回true,表示获取到了锁,线程逐级返回,加锁过程结束
parkAndCheckInterrupt线程进入阻塞状态
Node: n3thread = t3prev = n2next = null
判断当前线程是否为锁持有者
s 是第二个结点(第一个阶段是占位空结点),代表下一个应该获取锁的线程。s.thread != Thread.currentThread() 为 false,表示二者相等,即下一个可以获得锁的线程,正是当前线程,所以,此时整个 hasQueuedPredecssors 返回 false,表示不需要排队。
整个tryAcqurie返回 false,表示后去锁失败
false,表示锁被占用
整个 tryAcquire 方法返回true,表示获取锁成功
执行park逻辑
Node:n1thread = nullprev = nullnext = null
Thread.interrupt()
判断在当前线程前面,是否已经有其他线程在排队了,有,则表示当前线程也需要排队
队列已经初始化,将 node 加入到队列末尾,加入到队列后可能如下
false,执行上图步骤2
acquireQueued返回true,表示获取到锁,线程逐级返回,加锁结束。
成功获取到锁,设置锁持有线程为当前线程,返回
整个 AQS 的核心和难点之一。注意这里使用了 for(;;)首先判断 node 的前驱结点是不是 head,如果是,说明它是下一个可以获取锁的线程,则调用一次 tryAcquire() 方法尝试获取锁,若获取到,则将重新维护链表的结点关系(将node设置为 head,之前的 head 从链表中移除),然后返回。如果 node 的前驱结点不是 head,或者获取锁失败,则再判断其前驱结点的 waitStatus 是不是 SIGNAL,如果是则当前线程调用 park,进入阻塞状态。如果不是:1、==0,则将其设置为 SIGNAL;2、> 0(== 1)则表示前驱结点已经被取消,将取消的结点从链表中移出,再次维护链表的结点关系,然后再次进入 for 循环,按照上面的逻辑重新执行。
1
true,执行上图步骤1
true,表示锁自由态
sync.lock()
二者不相等,说明下一个可以获得锁的线程,不是当前线程,所以整个 hasQueuedPredecessors 返回 true,表示需要排队
没有其他线程在排队,调用 enq 构造队列,并将 node 加入到队列
c == 0
Node: n2thread = t2prev = n1next = null
tryAcquire方法执行流程详解
lock()
这部分所有逻辑,都是 tryAcquire 方法调用引申出来的,tryAcquire 方法,顾名思义,就是尝试获取一次锁
调用 setExclusiveOwnerThread,设置当前线程为锁持有线程
尝试获取锁,如果获取不到,则入队,并阻塞线程
元素个数 > 1?
0 条评论
回复 删除
下一页