Java并发(十):独占式超时获取同步状态

//让头结点可以回收

p.next = null; // help GC

//failed为false代表自旋过程无出错

failed = false;

//自旋结束,返回true,回到上一层继续运行

return true;

}

//上一个不是头结点,或者虽然是头结点但抢不到锁

//计算还需要等待多长时间

nanosTimeout = deadline - System.nanoTime();

//如果没有时间剩余了

if (nanosTimeout <= 0L)

//返回false

//上一层处理

return false;

//还有时间剩余,看需不需要park

//这里一样是自旋两次,然后就会返回true,代表需要Park

//继续判断剩余时间是否足够进行park

//因为如果park需要的时间都要多于线程剩下的时间

//没什么必要park了,让其再自旋多一次,拿不到就超时返回false

if (shouldParkAfterFailedAcquire(p, node) &&

nanosTimeout > spinForTimeoutThreshold)

//如果超时了,而且需要park,那就park掉

//这里park掉,线程也是会继续进行倒计时的

//在这段时间内都会被park掉,这段时间内,只有unpark和interrupt可以恢复

//超过这个时间也会恢复,然后又一次自旋,然后超时GG

LockSupport.parkNanos(this, nanosTimeout);

//判断唤醒该线程的方式是不是被别的线程interrupt

//因为park是不会覆盖interrupt标志位的

if (Thread.interrupted())

//如果是被中止

//抛出异常

throw new InterruptedException();

}

} finally {

//同理,没有修改过failed变量,抛出异常,超时

//就会执行下面cancelAcquire

if (failed)

cancelAcquire(node);

}

}

总结一下整个流程

  • 判断等待时间是否合理

  • 小于等于0,不合理,直接返回false

  • 计算最大等待时间点,使用当前虚拟机的时间加上等待时间,纳秒级别

  • 将当前线程添加到队列中

  • 定义一个failed变量,该变量用来表示该线程最终的结果是否拿到锁,开始为false,因为刚开始没拿到

  • 开始进行自旋

  • 当前线程查看自己的上一个结点是不是头结点

  • 如果是,再判断自己能不能抢到锁

  • 如果抢到了锁,调整线程队列,将自己设为头结点,并且断开旧头结点与队列的连接,让其被gc回收,然后修改failed变量为true,代表拿到了锁

  • 如果上一个结点不是头结点,或者,是头结点但抢不到锁

  • 计算当前线程还剩下多少等待时间,使用最大等待时间点减去当前时间点

  • 如果等待时间小于等于0,那就代表超时了,返回false

  • 如果没有超时,判断这个线程需不需要park掉

  • 如果自旋已经两次了,且剩余时间还足够,那就park掉,这个park会在指定一段时间后恢复回来,这一段时间就是指线程剩余的时间

  • 如果在剩余时间没有被主动唤醒,自己也会醒,但下一轮的自旋就会被判断超时了

  • 如果在剩余时间被主动唤醒,唤醒的动作有两种,一种是unpark,一种是interrupt

  • 判断唤醒是不是interrupt方式,如果是,那就直接抛出异常

  • 最后执行finally里面的判断

  • 如果failed变量为true,也就是该线程一直获取不到锁,就会执行cancelAcquire方法

spinForTimeoutThreshold

下面我们来看一下,是如何判断剩余时间是否足够进行park的

在这里插入图片描述

从代码上来看,只要大于spinForTimeoutThreshold就i是剩余时间足够进行park

在这里插入图片描述

而这个spinForTimeoutThreshold具体的数值为1000,也就是1000纳秒

parkNanos

在这里插入图片描述

这个方法就是在指定时间内将线程park,并且等超时了之后,就会恢复

可以看到,里面使用了内存屏障来保证线程安全的

cancalAcquire

这个方法就是当获取不到锁的时候,就会执行

获取不到锁,那当然就是将线程从队列中弹出呀,并且如果线程前面有一些其他线程也是取消状态的,顺便也要弹出,即不但要取消自己,还要取消前面的一些不要的线程

源码如下

需要文中资料的朋友,可以加我\/信获取:vip1024b 备注Java

private void cancelAcquire(Node node) {

//判断要移除的线程在不在

if (node == null)

//不在就直接返回

return;

//将结点里面的线程删除

node.thread = null;

// Skip cancelled predecessors

//获取结点的上一个结点

//下面就是针对前面的一些被取消的结点的操作

//这些结点也是要被删除的

Node pred = node.prev;

//循环判断上一个结点的状态是否大于0,如果大于0代表就要被删除

while (pred.waitStatus > 0)

//进入到这里就判断上一个结点要删除了

//所以让Node与上上个结点连接,

//同时将pred变为上上个结点,为了下一次循环

//这一步可以视为两步

//pred = pred.prev

//node.prev = pred

node.prev = pred = pred.prev;

//循环在这里就结束了,当初就是在这里卡了好久

//现在已经让正常的结点node.prev为正常的结点

//但还没有进行断开,下面的操作就是断开的

//pred此时就是正常的结点

//predNext就是前面一个被取消的结点

//下面只需要对这个predNext删除就行,因为这段的线程都是取消的

Node predNext = pred.next;

//将线程状态改为cancelled

node.waitStatus = Node.CANCELLED;

//如果线程是尾结点

//那么就CAS将尾结点换成Pred,也就是此时尾结点变成了pred

if (node == tail && compareAndSetTail(node, pred)) {

//然后CAS将pred后面的线程全部删除,改为Null

//这里就完全断开了需要删除的结点,并且正常的结点成为了尾结点

compareAndSetNext(pred, predNext, null);

}

//下面这一步,针对node不是尾结点

//或者node是尾结点,但在替换过程中,新插入了结点

else {

int ws;

//这里是针对,如果前面找到的最近的正常结点不是头结点

//如果不是头结点,将正常结点改为休眠状态,因为此时要进行调整队列

if (pred != head &&

((ws = pred.waitStatus) == Node.SIGNAL ||

(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&

最后

现在正是金三银四的春招高潮,前阵子小编一直在搭建自己的网站,并整理了全套的**【一线互联网大厂Java核心面试题库+解析】:包括Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等**

image
tus) == Node.SIGNAL ||

(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&

最后

现在正是金三银四的春招高潮,前阵子小编一直在搭建自己的网站,并整理了全套的**【一线互联网大厂Java核心面试题库+解析】:包括Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等**

[外链图片转存中…(img-9Rd8CGrd-1716310573799)]

  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值