锁 - AQS

 

目录

参考

“何时出队列?”和“如何出队列?

释放锁:

何时初始化队列 、如何入队?


参考

AQS:  https://blog.youkuaiyun.com/anlian523/article/details/106752414  

AQS 在 ThreadPoolExecutor中的应用;

“何时出队列?”和“如何出队列?

下面我们从“何时出队列?”和“如何出队列?

”两个方向来分析一下acquireQueued源码:

acquireQueued()方法解释了 node获取锁失败之后, “何时被中断”、中断之后又是“何时出队列”,和“如何出队列” ;

关键字:

for (;;)    +    waitStatus  +  LockSupport.park(this);

线程获取锁失败之后, 通过 

addWaiter(Node.EXCLUSIVE), arg)

方法把当前线程封装成Node追加到等待队列(双向链表);

再调 

acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 方法,该方法的入参正是当前线程构建的Node node

我们可以看到,

以自旋的方式做了下面步骤:

先找node的前驱节点,如果前驱节点是head头结点, 说明此事node排在“队首”, 是第一个有效节点,则去争抢锁;

不然的话,调用

shouldParkAfterFailedAcquire(p, node)

方法判断是否需要中断当前线程, 如果前驱节点的   waitStatus = -1 , SIGNAL, 则需要中断当前线程;

如果前驱节点的 waitStatus > 0,即  CANCELLED的, 则自旋跳过所有的 CANCELLED节点,并将node重新指向;

因为上层方法通过for(;;)自旋,这里最后会将前驱节点的waitStatus置位-1,即SIGNAL, 然后返回true,告知可以中断当前线程了;

之后调了

parkAndCheckInterrupt()方法去中断当前线程;

LockSupport.park(this);  中断线程; 线程此刻被中断,直到 通过upPark()方法唤醒;   ===》 什么时候唤醒呢?
被唤醒了之后, 会继续执行以下方法

Thread.interrupted() 用于查询当前线程的中断标识状态,并重置中断标识状态为false; 

 如果不重置中断标识状态,当为true时, 假如唤醒之后仍然获取锁失败, 此时还需要再调LockSupport.park(this)方法中断, 但是此时中断标识状态为true的话,将不能中断线程; 所以通过Thread.interrupted()重置中断标识状态为false;https://cgiirw.github.io/2018/05/27/Interrupt_Ques/

通过for(;;) 自旋,最终会不停尝试获取锁,获取失败就中断等待,当被唤醒之后再次自旋获取锁,周而复始, 直到获取锁成功;

如果中间抛了异常,会执行 finally里的  cancelAcquire(node); 方法;

自旋, 先判断首否首第一个有效节点,再获取锁,获取失败时, 判断首否需要中断,

释放锁:

释放锁成功之后, 会用头结点作为参数,唤醒队列中的后继节点

我们发现,只有h != null && h.waitStatus !=0 的时候才会唤醒,换句话说,

没有头结点时说明没有等待线程,

有头结点,且头节点的waitStatus  == 0时,也表示没有等待线程节点,

                     头节点的waitStatus  != 0时, 表示有等待线程节点;

ReentrantLock
 公平模式和非公平模式对lock()接口的实现不同点:

非公平模式锁会首先通过CAS争抢锁;

何时初始化队列 、如何入队?

 获取不到锁时,需要追加到队列尾部,当没有队列时,需要初始化,

tail == null时, 表示没有队列;

通过CAS,即

compareAndSetHead(new Node()

方法,控制只会生成一个队列,而不会因为并发导致生成另个队列;

之后将head和tail都指向这个"虚节点"(里面不存放线程,故叫“虚”节点);

再通过自旋的方式,把封装有线程的node追加到队列里;

如果一个线程未获取到锁, 什么情况下会挂起(或暂停)该线程呢?

如果一个线程未获取到锁,会自旋不停的判断前驱节点是不是头结点,如果是头节点而且能抢占锁成功,则获取锁成功,将头结点(即node的前驱节点释放掉GC),并将head指向自己;

如果前驱不是头结点,或争抢锁失败,则需要判断 前驱节点的waitStatus 是否是SIGNAL,只有当 waitStatus = SIGNAL时,才会尝试挂起node的线程;

通过 

parkAndCheckInterrupt() 方法挂起线程

挂起后,线程状态变成 WAITING,(参考线程的几种状态: 就绪态、等待态等 ,参考: ),  它需要等待一个unpark() 或中断 interrupt();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值