ReentrantLock.lock()方法的底层
2022-09-13 19:42:33 1 举报
以ReentrantLock的非公平锁为例,解析AQS的源码
作者其他创作
大纲/内容
true
prev
获取锁成功
acquireQueued返回,表示线程获取到锁,线程逐级返回,加锁过程结束
这个方法,接收两个Node对象参数,node是准备执行park操作的节点,pred是其前辈节点
这部分是node加入队列后的处理逻辑,这里会有一次自旋,尝试获取锁,获取不到,才调用park,阻塞自己
tail
false
表示获取到锁:1.将node设置为头结点2. 释放之前的头结点这个逻辑执行完成之后,node节点就不再排队了(获取到锁的线程,不会在排队队列中)
pred.waitStatus ==Node.SIGNAL
执行排队逻辑
next
pred!=null
非公平锁
详解
注意这里是设置pred节点,-1表示节点处于阻塞状态了每个node的waitStatus,在后继节点加入时设置
sync.lock()
Node :n2thread=Bprev=n1next=null
再次循环判断
公平锁
Node :n1thread=nullprev=nullnext=n2
aqs队列(链表)
ReentrantLock
Node :n3thread=Cprev=n2next=null
返回node
false 再次循环
NonFairSync.lock()
pred.waitStatus>0
FairSync.lock()
head
返回false,表示线程没有获取到锁
获取锁失败
队列已经初始化,node加入到队列的末尾,加入后队列可能如下
Sync extends AbstractQueuedSynchronizer
addWaiter
两种情况会导致阻塞结束1.持有锁的线程,释放锁之后,将这个线程unpark了。此时该线程,一定排在队列的队头,不包括head节点2. 线程被interrupt了,注意,在外部interrupt这个线程是不会抛出InterruptException,这一点和sleep、wait的阻塞不一样
tryAcquire
AQS有两个属性head和tail,分别用来保存aqs队列的头尾节点,初始时,这两个属性都是null。当有一个线程在排队时,队列如下
队列尚未初始化,调用enq方法,该方法生成一个空的Node对象,插入到aqs队列的头部,然后将参数node,作为其后继节点,插入队列,方法执行完成后,队列如下图
将当前线程封装成Node对象,并加入排队队列中根据排队队列是否执行过初始化,执行①和②不同的处理逻辑①:表示排队队列不为空,即已经初始化过了,此时只需将新的node加入排队队列的队尾即可②:表示排队队列为空,需执行队列初始化。enq方法会初始化一个空的Node,作为排队队列的head,然后将需要排队的线程,作为head的next节点插入,同时将该node的prev指针指向head
parkAndCheckInterrupt线程进入阻塞状态
acquire(1)
abstract void lock()
执行park逻辑
返回false
返回true
设置pred.waitStatus=-1
CAS获取锁
tryAcquire()
整个aqs的核心和难点之一(注意这里使用了for(;;)——死循环)1. 首先判断node的前辈节点是不是 head:(1)如果是,说明它是下一个可以获得锁的线程,则调用一次tryAcquire,尝试获取锁,若获取到,则将链表关系重新维护一下(node设置为head,之前的head从链表中移出),然后返回(2)如果node的前辈节点不是head,或获取锁失败,再判断其前辈节点的waitState2. 判断其前辈节点的waitState是不是SIGNAL(-1),(1)如果是,则当前线程调用park,进入阻塞状态;(2)如果不是,判断如下取值 ==0,则设置为SIGNAL >0(==1),则表示前辈节点已经被取消了, 将取消的节点,从队列中移出,重新维护排队链 表关系3. 然后再进入for循环,上面的逻辑重新执行一遍注意和doAcquireInterruptibly方法对比,二者区别主要在,发现线程被中断过后的处理逻辑
lock()
p==head?表示查询node是不是第一个排队的
acquireQueued
Node :n2thread=Bprev=n1next=n3
addWaiter详解
成功获取锁,设置持有线程为当前线程,返回
没有其他线程在排队,调用enq构造队列,并将node加入到队列
阻塞结束
返回true,表示线程获取到了锁,线程逐级返回,加锁过程结束
尝试获取锁,如果获取不到,排队(阻塞当前线程)
0 条评论
下一页