因此会先不断确认前继节点的实际状态,在只能阻塞的情况下才会去阻塞 。
并且会过滤掉cancel的线程节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 获取前继节点的等待状态 int ws = pred.waitStatus; // 如果等待状态为Node.SIGNAL(-1),则直接返回true即可以阻塞 // 因为这说明前继节点完成资源的释放或者中断后,会主动唤醒后继节点的(这也即是signal信号的含义),因此方法外面不用再反复CAS了,直接阻塞吧 if (ws == Node.SIGNAL) return true; // 如果前继节点的等待值大于0即CANCELLED(1),说明前继节点的线程发生过cancel动作 // 那就继续往前遍历 , 直到当前节点的前继节点的状态不为cancel if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 前继节点的等待状态不为SIGNAL(-1),也不为Cancel(1) // 那么只能是PROPAGATE(-3)或者CONDITION(-2)或者INITIAL(0) // 直接设置成SIGNAL,下一次还没CAS成功,就直接睡觉了 // 因此在前面所有节点没辩护的情况下,最多一次之后就会返回true让外面阻塞 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false;}5.parkAndCheckInterrupt() 阻塞线程使用LockSupport.park来阻塞当前这个对象所在的线程
private final boolean parkAndCheckInterrupt() { LockSupport.park(this);// 确认是否是中断导致的park结束,并清除中断标记 return Thread.interrupted();}public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null);}lockSupport.park()和普通的wait|notify都有啥区别?
- 面向的主体不一样 。LockSuport主要是针对Thread进进行阻塞处理,可以指定阻塞队列的目标对象,每次可以指定具体的线程唤醒 。Object.wait()是以对象为纬度,阻塞当前的线程和唤醒单个(随机)或者所有线程 。
- 实现机制不同 。虽然LockSuport可以指定monitor的object对象,但和object.wait(),两者的阻塞队列并不交叉 。可以看下测试例子 。object.notifyAll()不能唤醒LockSupport的阻塞Thread.
6.总体流程图
文章插图
代码中频繁出现的interruptd中断标记是做什么用的?对线程调用 t1.interrupt();时
会导致 LockSupport.park() 阻塞的线程重新被唤醒
即有两种唤醒情况: 被前置节点唤醒,或者被外部中断唤醒
这时候要根据调用的acuire类型决定是否在中断发生时结束锁的获取 。
上面介绍的是不可中断锁 。
在parkAndCheckInterrupt中,当park结束阻塞时时,使用的是 Thread.interrupted() 而不是 .isInterrupted() 来返回中断状态
因为前者会返回线程当前的中断标记状态同时清除中断标志位(置为false)
外层CAS循环时, 就不会让线程受中断标记影响,只是记录一下是否发生过中断
文章插图
当获取锁成功后,如果发现有过线程中断,则会触发中断异常,
文章插图
之后便由获取锁的调用者自己决定是否要处理线程中断 。像下面这样:
reentrantLock.lock();try { System.out.println("t1"); TimeUnit.SECONDS.sleep(30);} catch (InterruptedException e) { e.printStackTrace();} finally { reentrantLock.unlock();}那么另一种情况就是可中断锁了 。
ReentranLock有一个lockInterruptibly()方法就是这种情况
线程被唤醒时,如果发现自己被中断过,就会直接抛异常而不是继续获取锁
文章插图
因此如果你的线程对中断很敏感,那么就是用可中断锁,及时响应 。
如果不敏感,也要注意处理中断异常 。
AQS的详细资源释放流程首先AQS提供的模板方法为release方法 。
核心逻辑就是对资源进行尝试性释放
如果成功 , 就唤醒等待队列中的第一个头节点
public final boolean release(int arg) { // 是否释放成功,tryRelease是子类要实现的方法 if (tryRelease(arg)) { Node h = head; // 判断头节点是否正在阻塞中,是的话唤醒 if (h != null && h.waitStatus != 0) // 唤醒头节点 unparkSuccessor(h); return true; } return false; }
推荐阅读
- Briefings in Bioinformatics-2021 知识图谱-生物信息学-医学顶刊论文:生物信息学中的图表示学习:趋势、方法和应用
- golang中的nil接收器
- golang中的字符串
- DevOps|从特拉斯辞职风波到研发效能中的不靠谱人干的荒唐事
- 月圆之夜镜中的记忆全成就攻略是什么
- 怎样转发微信内容到朋友圈(如何将微信中的内容转发到朋友圈)
- 4 Java I/O:AIO和NIO中的Selector
- 时空中的绘旅人诸界归一出行服装羽翼获取方法是什么
- 时空中的绘旅人服装的获取方法是什么
- 划拳中的十五、二十怎么玩啊(划拳必赢的十大技巧)