场景:多个线程,顺序打印出1-30的数据;
public class Test {public static void main(String[] args) {Data data = new Data();new Thread(() -> {for (int i = 0; i < 10; i++) {data.printA();}}, "A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {data.printB();}}, "B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {data.printC();}}, "C").start();}}class Data{Lock lock = new ReentrantLock();Condition condition1 = lock.newCondition();Condition condition2 = lock.newCondition();Condition condition3 = lock.newCondition();private int num = 1;private int size = 0;public void printA() {lock.lock();try {while (num != 1) {condition1.await();}System.out.println(Thread.currentThread().getName() + ":" + size ++);num = 2;condition2.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void printB() {lock.lock();try {while (num != 2) {condition2.await();}System.out.println(Thread.currentThread().getName() + ":" + size ++);num = 3;condition3.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public void printC() {lock.lock();try {while (num != 3) {condition3.await();}System.out.println(Thread.currentThread().getName() + ":" + size ++);num = 1;condition1.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}
执行结果:
A:1B:2C:3A:4B:5C:6A:7B:8C:9A:10B:11C:12A:13B:14C:15A:16B:17C:18A:19B:20C:21A:22B:23C:24A:25B:26C:27A:28B:29C:30

// 唤醒等待了最久的线程// 其实就是,将这个线程对应的 node 从条件队列转移到阻塞队列public final void signal() {// 调用 signal 方法的线程必须持有当前的独占锁if (!isHeldExclusively())throw new IllegalMonitorStateException();Node first = firstWaiter;if (first != null)doSignal(first);}// 从条件队列队头往后遍历,找出第一个需要转移的 node// 因为前面我们说过,有些线程会取消排队,但是可能还在队列中private void doSignal(Node first) {do {// 将 firstWaiter 指向 first 节点后面的第一个,因为 first 节点马上要离开了// 如果将 first 移除后,后面没有节点在等待了,那么需要将 lastWaiter 置为 nullif ( (firstWaiter = first.nextWaiter) == null)lastWaiter = null;// 因为 first 马上要被移到阻塞队列了,和条件队列的链接关系在这里断掉first.nextWaiter = null;} while (!transferForSignal(first) &&(first = firstWaiter) != null);// 这里 while 循环,如果 first 转移不成功,那么选择 first 后面的第一个节点进行转移,依此类推}// 将节点从条件队列转移到阻塞队列// true 代表成功转移// false 代表在 signal 之前,节点已经取消了final boolean transferForSignal(Node node) {// CAS 如果失败,说明此 node 的 waitStatus 已不是 Node.CONDITION,说明节点已经取消,// 既然已经取消,也就不需要转移了,方法返回,转移后面一个节点// 否则,将 waitStatus 置为 0if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))return false;// enq(node): 自旋进入阻塞队列的队尾// 注意,这里的返回值 p 是 node 在阻塞队列的前驱节点Node p = enq(node);int ws = p.waitStatus;// ws > 0 说明 node 在阻塞队列中的前驱节点取消了等待锁,直接唤醒 node 对应的线程。唤醒之后会怎么样,后面再解释// 如果 ws <= 0, 那么 compareAndSetWaitStatus 将会被调用,上篇介绍的时候说过,节点入队后,需要把前驱节点的状态设为 Node.SIGNAL(-1)if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))// 如果前驱节点取消或者 CAS 失败,会进到这里唤醒线程,之后的操作看下一节LockSupport.unpark(node.thread);return true;}
