一、线程的阻塞:
在上一篇说到,线程获取同步状态失败的线程,会构造节点并加入到同步队列的尾部,然后通过自旋的方式不断的获取同步状态,但是在自旋过程中需要判断线程是否需要阻塞,我们再一次看一下acquire方法。
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } //也就是在这里需要判断线程是否需要阻塞 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; }
首先打开shouldParkAfterFailedAcquire方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { //前驱节点的等待状态 int ws = pred.waitStatus; //当前线程处于等待状态 返回true if (ws == Node.SIGNAL) return true; //说明前驱节点线程等待超时或者被中断,需要将前驱节点从同步队列中移除 if (ws > 0) { do { //从同步队列中移除前驱节点 node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; //状态为Condition或者propageate } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
从上述代码可以看出当前线程是否需要阻塞:
1、如果当前线程节点的前驱节点为SINGAL状态,则表明当前线程处于等待状态,返回true,当前线程阻塞
2、如果当前线程节点的前驱节点状态为CANCELLED(值为1),则表明前驱节点线程已经等待超时或者被中断,此时需要将该节点从同步队列中移除掉。最后返回false
3、如果当前节点节点前驱节点非SINGAL,CANCELLED状态,则通过CAS将其前驱节点的等待状态设置为SINGAL,返回false。
如果shouldParkAfterFailedAcquire方法返回true,则需要调用parkAndCheckInterrupt方法
private final boolean parkAndCheckInterrupt() { //阻塞当前线程 LockSupport.park(this); return Thread.interrupted(); }
parkAndCheckInterrupt方法会阻塞当前线程(使用LockSupport的park方法),并返回当前线程的中断状态。
二、线程的唤醒
当线程释放同步状态后,将唤醒其后继节点:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) //唤醒后继节点 unparkSuccessor(h); return true; } return false; }
唤醒线程使用unparkSuccessor方法
private void unparkSuccessor(Node node) { //头结点的等待状态 int ws = node.waitStatus; //如果头结点的状态小于0(初始状态) if (ws < 0) //使用CAS操作将头结点的状态设置为0 compareAndSetWaitStatus(node, ws, 0); //头结点的后继节点 Node s = node.next; //当后继节点为空或者等待超时或被中断 if (s == null || s.waitStatus > 0) { s = null; //从同步队列尾部开始,找到一个可以唤醒的节点 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } //唤醒后继节点 if (s != null) LockSupport.unpark(s.thread); }
在唤醒后继节点时,后继节点可能为空,等待超时,被中断,这个时候将从同步队列尾部开始寻找,寻找一个可唤醒的节点,然后调用unpark来唤醒该节点的线程。
三、LockSupport
从上面内容我们注意到,线程的阻塞和唤醒都使用到了LockSupport的方法,LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport定义了一组以park开头的方法用来阻塞线程,以及unpark来唤醒一个被阻塞的线程。
LockSupport提供的方法:
在Java6中,LockSupport增加了park(Object blocker),parkNanos(Object blocker,long nanos)和parkUtil(Object blocker,long deadline)三个方法,用于实现阻塞当前线程的功能。
其中blocker是用来标识当前线程在等待的对象。该对象主要用于问题排查和系统监控。
park方法
public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); UNSAFE.park(false, 0L); setBlocker(t, null); }
unpark方法
public static void unpark(Thread thread) { if (thread != null) UNSAFE.unpark(thread); }非法内部都是使用UNSAFE来实现的。