ReentrantLock加锁(非公平)
2021-04-18 11:48:07 0 举报
独占锁
作者其他创作
大纲/内容
NodeC
疑问:为什么在队列中会有诸多的CANCELLED节点?
将当前线程设置为绑定线程
成立?
是
-1
获取前节点的等待状态ws
1、head=node;2、head.thread=null;3、head.prev=null;
N
NodeA
1
标记原头结点可以垃圾回收了
成功?
当前线程==锁定线程?
成功
Node
注:此分支在独占锁的场景下不会走到,因为独占锁只有在最接近头结点的一个线程会被激活,所以此处pred恒等于head
准备加入阻塞队列
加锁失败
当前线程 == 锁定线程
1、pred != head2、pred.ws == SIGNAL || CAS(pred.ws = SIGNAL)3、pred.thread != null
0
否
AQS逻辑
执行CAS加锁操作
通过CAS将队尾的next指针设置为null
3
失败
发生异常后的处理动作
a
注:独占锁发生异常,一定是调用tryAcquire()方法导致的,而阻塞队列中同一时刻,只能有一个线程在执行tryAcquire()方法,所以在处理异常的过程中,不存在并发问题
如果当前节点是队尾,就尝试将pred设置为队尾
1、pred = pred.prev2、node.prev = pred
当前节点
通过CAS初始化头结点
新建独占模式节点 newNode
无休止挂起,调用方法为LockSupport.park(this)自此,当前线程开始等待被其他线程唤起;也就是在线程挂起之前,其waitStatus都会被置为-1
ws == 1
将当前节点的ws标记为CANCELLED
不论是否CAS执行成功,都将结束当前操作;因为如果失败的话,一定是有另外线程进入了阻塞队列,并加入了队尾
NodeB
尾节点 == null ?
重置头结点,即将当前节点设置为头结点,且初始化标记线程、prev属性
pred.ws==1?
问1:如果是普通节点,直接挂在队尾,且将其线程挂起,这个没啥问题;但如果是头节点被唤醒,尝试加锁却失败了,又被再次挂起,会不会导致头结点永远处于挂起状态?答1:不会,因为头结点之所以抢锁失败,一定是因为另外一个A线程抢锁成功。虽然头节点暂时处于挂起状态,但当A线程执行完加锁代码后,还会再次唤醒头结点问2:假定当前节点判定需要被挂起,在执行挂起操作前,拥有锁的线程执行完毕,并唤醒了当前线程,而当前线程又马上要进行挂起操作,岂不是会导致无法成功将当前节点唤醒,从而永远hang死?答2:能考虑到这个问题,说明你已经带着分身去思考问题了,不错。不过此处是不会存在这个问题的,因为线程挂起、唤醒使用的api为park/unpark,即便是unpark发生在park之前,在执行park操作时,也会成功唤醒。这个特质区别于wait/notify问3:如果头结点在加锁期间抛出异常,岂不是导致阻塞队列后续的节点得不到唤起?答3:不会,参见发生异常后的逻辑
通过cas操作,如果将state从 0 改为 1,即标识加锁成功
state==0?
2
通过CAS将newNode添加至队尾
线程挂起
是否可重入判断
将线程标记为空node.thread = null
将原头结点的next属性置为null
头结点一旦初始化,便一直存在。不过头结点不存储线程信息,也不会执行同步块代码。同步队列为空时,即只有一个头结点时
被其他线程唤醒
准备判断当前线程是否应该被挂起
加锁成功
ws == ?
通过CAS将状态从0改为-1
pred.ws == CANCELLED?
node == tail &&CAS(tail = pred)
主要做以下两件事儿:1、将当前线程挂起并放入阻塞队列2、头结点被唤醒后,尝试进行加锁
ReentrantLock逻辑
state=state+1
初始化之前,头尾节点都为null,而初始化的时候,头尾节点是一起进行的,成功初始化头结点的线程,同时负责将尾结点也初始化,并将头尾设置为同一个节点
tail = head;
newNode对应的waitStatus == 0
将新节点添加至队列尾部
pred = node.prev
ws的状态:0(初始状态)-1(SIGNAL)1(已取消)
执行加锁操作
返回当前线程是否标记中断。注:当前方法虽然标记了中断字段,但不会中途放弃,一定要拿到资源后才会返回
注:此处为可重入逻辑,将state状态累加,当然在解锁的时候,也要进行多次解锁;有同学问:设置新的state状态不需要加锁吗?万一有多个线程同时操作怎么办?其实此处不用加锁,因为在进入当前逻辑前已经判断了“当前线程==锁定线程”,即只可能有一个线程进入当前逻辑
pred = pred.prev node.prev = pred
新建头结点对应的waitStatus == 0
满足3个条件:1、pred不是头结点2、pred.ws等于-1或者 可将其通过CAS由0改为-13、pred.thread不为空
此处如果CAS操作失败,表明在同一时刻有多个线程在向尾部添加节点,所以再次尝试即可
上图举例的是,连续2个被激活的节点发生了异常。由此可见,当被激活线程发生异常后,其会主动与后续节点中断,并激活后续节点。但此时,在阻塞队列中,存在很多ws为CANCELLED类型的节点并不会被删除,而是在第二大部分的a模块清理掉的
ws == 0
将当前线程标记为锁定线程
将pred的下一个节点标记为predNextpredNext = pred.next
整个过程发生异常
ws == -1
node.next = node
state = nextc;
这一步的目的,是将当前节点之前所有状态码为CANCELLED(1)的节点从阻塞队列中删除;上述图例可能是会真实发生的,当NodeB节点作为头结点后的第一个节点被唤起后,执行加锁逻辑时发生了异常,NodeB的状态便会更改为CANCELLED,并同时唤醒NodeC
acquire
nextc = state + 1;
唤醒下一个节点
断开next链
preNode是头结点?
当前节点:node当前节点前节点:preNode
从node节点开始向前寻找第一个非CANCELLED的节点,并标记为pred,然后将node的前节点指向pred
收藏
0 条评论
回复 删除
下一页