写点什么

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

作者:Java高工P7
  • 2021 年 11 月 11 日
  • 本文字数:2509 字

    阅读完需:约 8 分钟

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

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


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


源码如下


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))) &&


pred.thread != null) {


Node next = node.next;


if (next != null && next.waitStatus <= 0)


//让正常结点指向 node 的后驱结点


//即断开了 node


compareAndSetNext(pred, predNext, next);


}


//如果是正常结点是头结点,


//那就直接唤醒后面的线程


else {


unparkSuccessor(node);


}


node.next = node; // help GC


}


}


步骤如下


  • 判断要移除的 node 线程是否存在

  • 不存在,直接结束

  • 移除的线程存在,接下来是循环将前面的一些也是要被取消的线程找出来

  • pred 记录最先不被取消的线程,predNext 记录最先不被取消的线程后面的线程,也就是 predNext 后面的线程全部应该被取消的

  • 将 node 线程的状态改为 cancel

  • 后面分为三种情况来进行处理

  • 如果要移除的 node 线程是尾结点

  • 那么将队列尾结点 CAS 从 node 替换为 pred

  • 然后 CAS 将 pred 的后驱结点从 predNext 改为 null

  • 这样就断开了所有被取消的结点

  • 如果要移除的 node 线程不是尾结点

  • 如果 pred(最先不被取消的线程)不是头结点

  • 将其改为休眠状态,因为此时 pred 是队列中最后一个线


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


程了,最后一个就先让他进行休眠?


  • CAS 让其的后驱结点 preNext 为 node 的后驱结点

  • 相当于将所有被取消的结点断开

  • 如果 pred 是头结点

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
Java并发(十):独占式超时获取同步状态