07 并发工具类CountDownLatch、CyclicBarrier、Semaphore使用及源码分析

359 阅读4分钟

在 JUC 下包含了一些常用的同步工具类,今天就来详细介绍一下,CountDownLatch,CyclicBarrier,Semaphore 的使用方法以及它们之间的区别。

1 CountDownLatch

1.1 功能描述

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

countdownlatch 提供了两个方法,一个是 countDown,一个是 await,countdownlatch初始化的时候需要传入一个整数,在这个整数倒数到 0之前,调用了 await 方法的程序都必须要等待,然后通过 countDown 来倒数。

1.2 简单案例

public class CountDownLatchDemo {

    private static final CountDownLatch countDownLatch = new CountDownLatch(3);

    public static void main(String[] args) {
        try {
            System.out.println("main线程开始执行");
            for (int i = 0; i < 3; i++) {
                new Thread(new CountDownLatchThread(), String.valueOf(i)).start();
                TimeUnit.SECONDS.sleep(1);
            }
            // 阻塞,直到countDownLatch为0
            // main线程等待3个子线程都执行完成会被唤醒
            countDownLatch.await();
            System.out.println("main线程执行完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class CountDownLatchThread implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println("子线程" + Thread.currentThread().getName() + "开始执行");
                TimeUnit.SECONDS.sleep(10);
                // 当前线程调用此方法,则计数减一
                countDownLatch.countDown();
                System.out.println("子线程" + Thread.currentThread().getName() + "执行完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

程序功能:main线程需要等待其他3个子线程执行结束后,才会继续执行后续的代码。有点类似 join 的功能,但是比 join 更加灵活。

join的功能:在当前线程中,如果调用某个thread的join方法,那么当前线程就会被阻塞,直到thread线程执行完毕,当前线程才能继续执行。join的原理是,不断的检查thread是否存活,如果存活,那么让当前线程一直wait,直到thread线程终止,线程的this.notifyAll 就会被调用。

与join的区别:假设子线程的工作都分为两个阶段,主线程只需要等待子线程完成第一个阶段就可以执行了。使用join是无法实现的,此时使用countDownLatch改变countDown()的位置即可。

1.3 源码分析

1.3.1 类图UML

image-20201009232803482

它的底层实现是基于 AQS 的共享锁。

1.3.2 初始化

CountDownLatch 使用了共享锁模式。CountDownLatch 使用了一个内部类 Sync来实现CountDownLatch的同步控制,而Sync是AQS的一个实现类,它使用AQS的状态(state)来表示count。

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) {
        setState(count);
    }

    int getCount() {
        return getState();
    }

    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

1.3.3 await()

导致当前的线程等待直到count被倒数到0,或者线程被中断。

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    // 线程被中断,抛出中断异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 尝试获取共享锁
    if (tryAcquireShared(arg) < 0)
      	// 获取锁失败,加入等待队列
        doAcquireSharedInterruptibly(arg);
}

tryAcquireShared只有当State为0才可以获取到同步锁

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

doAcquireSharedInterruptibly:获取共享锁失败加入等待队列(可中断),代码同ReentrantReadWriteLock中的ReadLoack,详见之前的代码分析。

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

1.3.4 countDown()

public void countDown() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    // 释放共享锁,当释放后State为0。唤醒共享锁的同步队列中等待的线程。
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

2 CyclicBarrier

2.1 功能描述

作用:让所有线程都等待完成后才会继续下一步行动。

栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。

CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。

2.2 简单案例

public class CyclicBarrierDemo {

    private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new BarrierAction());

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 4; i++) {
            new Thread(new BarrierThread(), String.valueOf(i)).start();
            TimeUnit.MILLISECONDS.sleep(1);
        }
    }

    static class BarrierThread implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println("线程: " + Thread.currentThread().getName() + " 启动");
                TimeUnit.SECONDS.sleep(5);
                cyclicBarrier.await();
                System.out.println("线程: " + Thread.currentThread().getName() + " 结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * CyclicBarrier可以再次使用
     */
    static class CyclicBarrierThread implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println("线程: " + Thread.currentThread().getName() + " 启动");
                cyclicBarrier.await();
                System.out.println("线程: " + Thread.currentThread().getName() + " 结束");

                TimeUnit.SECONDS.sleep(10);
                System.out.println("线程: " + Thread.currentThread().getName() + " 再次启动");
                cyclicBarrier.await();
                System.out.println("线程: " + Thread.currentThread().getName() + " 再次结束");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    static class BarrierAction implements Runnable {
        @Override
        public void run() {
            System.out.println("栅栏都到达后,第一个先执行的线程");
        }
    }
}

线程: 0 启动
线程: 1 启动
线程: 2 启动
线程: 3 启动
栅栏都到达后,第一个先执行的线程
线程: 3 结束
线程: 0 结束
线程: 2 结束
线程: 1 结束

把parties改为2后的输出结果是什么呢?

先执行两个后再执行另外两个。分两批执行。这个就是栅栏的循环使用

线程: 0 启动
线程: 1 启动
线程: 2 启动
线程: 3 启动
栅栏都到达后,第一个先执行的线程
线程: 1 结束
线程: 0 结束
栅栏都到达后,第一个先执行的线程
线程: 3 结束
线程: 2 结束

2.3 源码分析

image-20201009233245167

底层使用ReentrantLock 和 Condition来实现的。

2.3.1 初始化

/** The lock for guarding barrier entry */
// 可重入独占锁
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
// 等待队列
private final Condition trip = lock.newCondition();
/** The number of parties */
// 参与的线程数量
private final int parties;
/* The command to run when tripped */
// 由最后一个进入 barrier 的线程执行的操作
private final Runnable barrierCommand;
/** The current generation */
// 当前代
private Generation generation = new Generation();
// 正在等待进入屏障的线程数量
private int count;
private static class Generation {
    boolean broken = false;
}

CyclicBarrier中主要的成员变量。

  • CyclicBarrier 使用 ReentrantLock 和 Condition 类来构建。之前的源码分析已经分析了这两个类,具体可以参考之前的文章。
  • CyclicBarrier 类存在一个内部类 Generation,每一次使用 CyclicBarrier 可以当成 Generation 的实例。
public CyclicBarrier(int parties) {
    this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
  	// 参与的线程数量小于等于0,抛出异常
    if (parties <= 0) throw new IllegalArgumentException();
 		// 设置parties 
    this.parties = parties;
  	// 设置count
    this.count = parties;
  	// 设置barrierCommand
    this.barrierCommand = barrierAction;
}

CyclicBarrier有两个构造函数

CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示栅栏拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 当前线程已经到达了栅栏,然后当前线程被阻塞。

CyclicBarrier的另一个构造函数CyclicBarrier(int parties, Runnable barrierAction),用于线程到达栅栏时,优先执行barrierAction,方便处理更复杂的业务场景。由最后一个进入 barrier 的线程执行的操作。

2.3.2 await

调用await方法的线程告诉CyclicBarrier自己已经到达同步点,然后当前线程被阻塞,直到parties个参与线程调用了await方法。CyclicBarrier同样提供带超时时间的await和不带超时时间的await方法。

超时时间的await,时间到达后。会先破坏栅栏,之后唤醒所有的线程。

public int await() throws InterruptedException, BrokenBarrierException {
    try {
      	// 不超时等待
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
public int await(long timeout, TimeUnit unit)
    throws InterruptedException,
           BrokenBarrierException,
           TimeoutException {
    return dowait(true, unit.toNanos(timeout));
}

这两个方法最终都会调用dowait(boolean, long)方法,它也是CyclicBarrier的核心方法

2.3.3 dowait

dowait(boolean, long)方法的主要逻辑处理比较简单,如果该线程不是最后一个调用await方法的线程,则它会一直处于等待状态,除非发生以下情况:

  • 最后一个线程到达,即index == 0
  • 某个参与线程等待超时
  • 某个参与线程被中断
  • 调用了CyclicBarrier的reset()方法。该方法会将屏障重置为初始状态
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    // 获取独占锁         
    final ReentrantLock lock = this.lock;
    // 当前线程锁定         
    lock.lock();
    try {
        // 当前代
        final Generation g = generation;
				// 如果这代损坏了,抛出异常
        if (g.broken)
            throw new BrokenBarrierException();
				// 如果线程中断了,抛出异常
        if (Thread.interrupted()) {
          	// 将损坏状态设置为true
            // 并通知其他阻塞在此栅栏上的线程
            breakBarrier();
            throw new InterruptedException();
        }
				// 减少正在等待进入栅栏的线程数量
      	// 正在等待进入栅栏的线程数量
        int index = --count;
      	// 正在等待进入栅栏的线程数量为0,所有线程都已经进入
        if (index == 0) {  // tripped
          	// 运行的动作标识
            boolean ranAction = false;
            try {
              	// 初始化时的运行动作
                final Runnable command = barrierCommand;
                // 运行动作不为空
                if (command != null)
                  	// 运行
                    command.run();
              	// 设置ranAction状态
                ranAction = true;
                // 进入下一代,循环使用的重要原理
                nextGeneration();
                return 0;
            } finally {
              	// 上面发生异常
                if (!ranAction)
                  	// 损坏当前屏障
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
      	// 无限循环
        for (;;) {
            try {
              	// 没有设置等待时间
                if (!timed)
                  	// 当前线程等待,加入等待队列中
                    trip.await();
              	// 置等待时间
                else if (nanos > 0L)
                  	// 当前线程等待,加入等待队列中。超时后当前线程自动唤醒
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) { // 线程等待await中被中断
              	// 等于当前代并且栅栏没有被损坏
                if (g == generation && ! g.broken) {
                  	// 损坏当前栅栏
                    breakBarrier();
                    // 抛出异常
                    throw ie;
                } else {
                    // 上面条件不满足,说明这个线程不是这代的
                    // 就不会影响当前这代栅栏的执行,所以,就打个中断标记
                  	// 中断当前线程
                    Thread.currentThread().interrupt();
                }
            }
						// 当有任何一个线程中断了,就会调用breakBarrier方法
            // 就会唤醒其他的线程,其他线程醒来后,也要抛出异常
            if (g.broken)
                throw new BrokenBarrierException();
						// 不等于当前代
          	// g != generation表示正常换代了,返回当前线程所在栅栏的下标
            // 如果 g == generation,说明还没有换代,那为什么会醒了?
            // 因为一个线程可以使用多个栅栏,当别的栅栏唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。
            // 正是因为这个原因,才需要generation来保证正确。
            if (g != generation)
              	// 正在等待进入栅栏的线程数量
                return index;
						// 设置了等待时间,并且等待时间小于0
          	// await(long timeout, TimeUnit unit)
            // 栅栏破坏,唤醒所有await的线程
            if (timed && nanos <= 0L) {
              	// 损坏栅栏
                breakBarrier();
              	// 抛出异常
                throw new TimeoutException();
            }
        }
    } finally {
      	// 释放锁
        lock.unlock();
    }
}

我们可能需要注意Generation 对象,在上述代码中我们总是可以看到抛出BrokenBarrierException异常,那么什么时候抛出异常呢?如果一个线程处于等待状态时,如果其他线程调用reset(),或者调用的barrier原本就是被损坏的,则抛出BrokenBarrierException异常。同时,任何线程在等待时被中断了,则其他所有线程都将抛出BrokenBarrierException异常,并将barrier置于损坏状态。

同时,Generation描述着CyclicBarrier的更新换代。在CyclicBarrier中,同一批线程属于同一代。当有parties个线程到达barrier之后,generation就会被更新换代。其中broken标识该当前CyclicBarrier是否已经处于中断状态。

2.3.4 breakBarrier

当barrier损坏了或者有一个线程中断了,则通过breakBarrier()来终止所有的线程。

在breakBarrier()中除了将broken设置为true,还会调用signalAll将在CyclicBarrier处于等待状态的线程全部唤醒。

private void breakBarrier() {
  	// 设置状态
    generation.broken = true;
  	// 恢复正在等待进入栅栏的线程数量
    count = parties;
  	// 唤醒所有线程
    trip.signalAll();
}

2.3.5 nextGeneration

当所有线程都已经到达barrier处(index == 0),则会通过nextGeneration()进行更新换地操作,在这个步骤中,做了三件事:唤醒所有线程,重置count,generation

private void nextGeneration() {
    // signal completion of last generation
  	// 唤醒所有线程
    trip.signalAll();
    // set up next generation
  	// 重置正在等待进入屏障的线程数量
    count = parties;
    // 新生一代
    generation = new Generation();
}

除了上面讲到的栅栏更新换代以及损坏状态,我们在使用CyclicBarrier时还要要注意以下几点:

  • CyclicBarrier使用独占锁来执行await方法,并发性可能不是很高
  • 如果在等待过程中,线程被中断了,就抛出异常。但如果中断的线程所对应的CyclicBarrier不是这代的,比如,在最后一次线程执行signalAll后,并且更新了这个“代”对象。在这个区间,这个线程被中断了,那么,JDK认为任务已经完成了,就不必在乎中断了,只需要打个标记。该部分源码已在dowait(boolean, long)方法中进行了注释。
  • 如果线程被其他的CyclicBarrier唤醒了,那么g肯定等于generation,这个事件就不能return了,而是继续循环阻塞。反之,如果是当前CyclicBarrier唤醒的,就返回线程在CyclicBarrier的下标。完成了一次冲过栅栏的过程。该部分源码已在dowait(boolean, long)方法中进行了注释。

3 Semaphore

3.1 功能描述

semaphore 也就是我们常说的信号灯,semaphore 可以控制同时访问的线程个数,通过 acquire 获取一个许可,如果没有就等待,通过 release 释放一个许可。有点类似限流的作用。叫信号灯的原因也和他的用处有关,比如某商场就 5 个停车位,每个停车位只能停一辆车,如果这个时候来了 10 辆车,必须要等前面有空的车位才能进入。

3.2 简单案例

public class SemaphoreDemo {

    private static Semaphore semaphore = new Semaphore(5);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(new ParkCar(), "车辆 " + i).start();
            TimeUnit.MILLISECONDS.sleep(100);
        }
    }

    static class ParkCar implements Runnable {
        @Override
        public void run() {
            try {
                // 申请许可证类似于查看有无车位
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " 开始停车");
                // 模拟停车10s中
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 结束停车");
            // 释放许可证类似于车子驶离停车场,空出来了车位
            semaphore.release();
        }
    }
}

输出结果: 一次听了5辆车,当后面车子到来时,停车场无空的位子,需要等待前面的车辆驶离停车场,驶离一辆,后面就可以加入一辆车子。

车位一共就发 5 个,那等第一批车辆用完释放之后, 第二批的时候应该发给谁呢?

所有等待的车辆都想先拿到许可,先通行,怎么办。这就需要,用到锁了。就所有人都去抢,谁先抢到,谁就先停车。

车辆 0 开始停车
车辆 1 开始停车
车辆 2 开始停车
车辆 3 开始停车
车辆 4 开始停车
车辆 0 结束停车
车辆 5 开始停车
车辆 1 结束停车
车辆 6 开始停车
车辆 2 结束停车
车辆 7 开始停车
车辆 3 结束停车
车辆 8 开始停车
车辆 4 结束停车
车辆 9 开始停车
车辆 5 结束停车
车辆 6 结束停车
车辆 7 结束停车
车辆 8 结束停车
车辆 9 结束停车

3.3 源码分析

3.3.1 UML类图

WX20201009-232147

它的底层实现是基于 AQS 的共享锁。

3.3.2 初始化

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

从构造器里面可以看出来semaphore默认实现的是非公平锁,当然我们也可以指定锁的类型,是否为公平锁。

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

    NonfairSync(int permits) {
        super(permits);
    }

    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

Sync(int permits) {
    setState(permits);
}

protected final void setState(int newState) {
     state = newState;
}

我们可以看到NonfairSync类继承了Sync,而Sync继承了AQS,从这里其实可以看出来semaphore是基于AQS实现的。AQS中的同步状态值state存储许可证的个数

以下代码以非公平锁为例

3.3.3 acquire

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
  	// 如果线程中断则抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 申请共享锁
    if (tryAcquireShared(arg) < 0)
        // 申请共享锁失败,加入等待队列
        doAcquireSharedInterruptibly(arg);
}

3.3.4 tryAcquireShared

protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
  	// 自旋
    for (;;) {
      	// 获取许可数
        int available = getState();
        // 剩余许可数
        int remaining = available - acquires;
        // 剩余许可数大于0,当前线程可以执行。CAS修改许可数
        // 剩余许可数小于0,加入同步队列等待。
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

3.3.5 doAcquireSharedInterruptibly

许可数用完,加入同步队列。等待许可的释放。

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

前面分析ReentrantReadWriteLock.ReadLock详细分析过。

3.3.6 release

public void release() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

此处sync调用了AQS中的方法releaseShared,在这个方法中如果释放成功那么就调用doReleaseShared方法,此方法在前面AQS共享模式一文中已经讲解过,此处不在详细讲解。它主要作用就是释放队列中的节点。

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
      	// 获取当前许可数
        int current = getState();
        // 释放后的许可数
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        // 更新新的许可数
        if (compareAndSetState(current, next))
            return true;
    }
}
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}