JUC中的AQS底层详细超详解( 三 )


因此会先不断确认前继节点的实际状态,在只能阻塞的情况下才会去阻塞 。
并且会过滤掉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都有啥区别?

  1. 面向的主体不一样 。LockSuport主要是针对Thread进进行阻塞处理,可以指定阻塞队列的目标对象,每次可以指定具体的线程唤醒 。Object.wait()是以对象为纬度,阻塞当前的线程和唤醒单个(随机)或者所有线程 。
  2. 实现机制不同 。虽然LockSuport可以指定monitor的object对象,但和object.wait(),两者的阻塞队列并不交叉 。可以看下测试例子 。object.notifyAll()不能唤醒LockSupport的阻塞Thread.
如果还要深挖底层实现原理 , 可以详细见该链接简而言之,是用mutex和condition保护了一个_counter的变量,当park时,这个变量置为了0 , 当unpark时 , 这个变量置为1 。底层用的C语言的pthread_mutex_unlock、pthread_cond_wait 、pthread_cond_signal  , 但是针对了mutex和_cond两个变量进行加锁 。
6.总体流程图
JUC中的AQS底层详细超详解

文章插图
代码中频繁出现的interruptd中断标记是做什么用的?对线程调用 t1.interrupt();时
会导致 LockSupport.park() 阻塞的线程重新被唤醒
即有两种唤醒情况: 被前置节点唤醒,或者被外部中断唤醒
这时候要根据调用的acuire类型决定是否在中断发生时结束锁的获取 。
上面介绍的是不可中断锁 。
在parkAndCheckInterrupt中,当park结束阻塞时时,使用的是 Thread.interrupted() 而不是 .isInterrupted() 来返回中断状态
因为前者会返回线程当前的中断标记状态同时清除中断标志位(置为false)
外层CAS循环时, 就不会让线程受中断标记影响,只是记录一下是否发生过中断
JUC中的AQS底层详细超详解

文章插图
当获取锁成功后,如果发现有过线程中断,则会触发中断异常,
JUC中的AQS底层详细超详解

文章插图
之后便由获取锁的调用者自己决定是否要处理线程中断 。像下面这样:
reentrantLock.lock();try { System.out.println("t1"); TimeUnit.SECONDS.sleep(30);} catch (InterruptedException e) { e.printStackTrace();} finally { reentrantLock.unlock();}那么另一种情况就是可中断锁了 。
ReentranLock有一个lockInterruptibly()方法就是这种情况
线程被唤醒时,如果发现自己被中断过,就会直接抛异常而不是继续获取锁
JUC中的AQS底层详细超详解

文章插图
因此如果你的线程对中断很敏感,那么就是用可中断锁,及时响应 。
如果不敏感,也要注意处理中断异常 。
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; }

推荐阅读