join、CountDownLatch、CyclicBarrier浅析
1、用法示例
(1)join是Thread提供的线程间协作的方法,通过查看代码可知是通过自旋wait实现的,使用方法比较简单,直接调用线程的join方法就会进入wait状态,直到该线程 !isAlive() 跳出循环,从而保证线程的执行顺序,适用于线程间执行的逻辑有依赖的情况。具体代码如下:
public final void join() throws InterruptedException { join(0); }
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
public final synchronized void join(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } join(millis); }
具体使用方法如下:
public static void main(String[] args) { Random random = new Random(); Runnable runnable = () -> { log.info("Runnable is starting ..."); int sleep = random.nextInt(1000); try { Thread.sleep(sleep); } catch (InterruptedException e) { e.printStackTrace(); } log.info("Runnable{} is finished and work cost {}ms ...", Thread.currentThread().getName(), sleep); }; Thread t1 = new Thread(runnable); Thread t2 = new Thread(runnable); Thread t3 = new Thread(runnable); // 此处t1、t2调用join方法后其他线程会等待t1、t2执行结束后开始 t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } t3.start(); }
运行结果如下:
2019-07-16 14:29:57.847 [Thread-0] INFO com.longc.demo.study.thread.TestJoin - Runnable is starting ... 2019-07-16 14:29:57.847 [Thread-1] INFO com.longc.demo.study.thread.TestJoin - Runnable is starting ... 2019-07-16 14:29:58.146 [Thread-0] INFO com.longc.demo.study.thread.TestJoin - RunnableThread-0 is finished and work cost 292ms ... 2019-07-16 14:29:58.304 [Thread-1] INFO com.longc.demo.study.thread.TestJoin - RunnableThread-1 is finished and work cost 450ms ... 2019-07-16 14:29:58.305 [Thread-2] INFO com.longc.demo.study.thread.TestJoin - Runnable is starting ... 2019-07-16 14:29:59.260 [Thread-2] INFO com.longc.demo.study.thread.TestJoin - RunnableThread-2 is finished and work cost 951ms ...
由运行结果可以看出线程2等待线程0、线程1执行结束后才开始执行。
(2)CountDownLatch是通过共享锁实现的一个线程间协作工具。利用的是AQS中的status(可以理解为锁的数量),逐渐减少锁直至全部锁释放掉,因此其核心是内部类Sync及AQS的原理,具体代码如下:
/* * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ /* * * * * * * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ package java.util.concurrent; import java.util.concurrent.locks.AbstractQueuedSynchronizer; /** * A synchronization aid that allows one or more threads to wait until * a set of operations being performed in other threads completes. * * <p>A {@code CountDownLatch} is initialized with a given <em>count</em>. * The {@link #await await} methods block until the current count reaches * zero due to invocations of the {@link #countDown} method, after which * all waiting threads are released and any subsequent invocations of * {@link #await await} return immediately. This is a one-shot phenomenon * -- the count cannot be reset. If you need a version that resets the * count, consider using a {@link CyclicBarrier}. * * <p>A {@code CountDownLatch} is a versatile synchronization tool * and can be used for a number of purposes. A * {@code CountDownLatch} initialized with a count of one serves as a * simple on/off latch, or gate: all threads invoking {@link #await await} * wait at the gate until it is opened by a thread invoking {@link * #countDown}. A {@code CountDownLatch} initialized to <em>N</em> * can be used to make one thread wait until <em>N</em> threads have * completed some action, or some action has been completed N times. * * <p>A useful property of a {@code CountDownLatch} is that it * doesn't require that threads calling {@code countDown} wait for * the count to reach zero before proceeding, it simply prevents any * thread from proceeding past an {@link #await await} until all * threads could pass. * * <p><b>Sample usage:</b> Here is a pair of classes in which a group * of worker threads use two countdown latches: * <ul> * <li>The first is a start signal that prevents any worker from proceeding * until the driver is ready for them to proceed; * <li>The second is a completion signal that allows the driver to wait * until all workers have completed. * </ul> * * <pre> {@code * class Driver { // ... * void main() throws InterruptedException { * CountDownLatch startSignal = new CountDownLatch(1); * CountDownLatch doneSignal = new CountDownLatch(N); * * for (int i = 0; i < N; ++i) // create and start threads * new Thread(new Worker(startSignal, doneSignal)).start(); * * doSomethingElse(); // don't let run yet * startSignal.countDown(); // let all threads proceed * doSomethingElse(); * doneSignal.await(); // wait for all to finish * } * } * * class Worker implements Runnable { * private final CountDownLatch startSignal; * private final CountDownLatch doneSignal; * Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { * this.startSignal = startSignal; * this.doneSignal = doneSignal; * } * public void run() { * try { * startSignal.await(); * doWork(); * doneSignal.countDown(); * } catch (InterruptedException ex) {} // return; * } * * void doWork() { ... } * }}</pre> * * <p>Another typical usage would be to divide a problem into N parts, * describe each part with a Runnable that executes that portion and * counts down on the latch, and queue all the Runnables to an * Executor. When all sub-parts are complete, the coordinating thread * will be able to pass through await. (When threads must repeatedly * count down in this way, instead use a {@link CyclicBarrier}.) * * <pre> {@code * class Driver2 { // ... * void main() throws InterruptedException { * CountDownLatch doneSignal = new CountDownLatch(N); * Executor e = ... * * for (int i = 0; i < N; ++i) // create and start threads * e.execute(new WorkerRunnable(doneSignal, i)); * * doneSignal.await(); // wait for all to finish * } * } * * class WorkerRunnable implements Runnable { * private final CountDownLatch doneSignal; * private final int i; * WorkerRunnable(CountDownLatch doneSignal, int i) { * this.doneSignal = doneSignal; * this.i = i; * } * public void run() { * try { * doWork(i); * doneSignal.countDown(); * } catch (InterruptedException ex) {} // return; * } * * void doWork() { ... } * }}</pre> * * <p>Memory consistency effects: Until the count reaches * zero, actions in a thread prior to calling * {@code countDown()} * <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a> * actions following a successful return from a corresponding * {@code await()} in another thread. * * @since 1.5 * @author Doug Lea */ public class CountDownLatch { /** * Synchronization control For CountDownLatch. * Uses AQS state to represent 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; } } } private final Sync sync; /** * Constructs a {@code CountDownLatch} initialized with the given count. * * @param count the number of times {@link #countDown} must be invoked * before threads can pass through {@link #await} * @throws IllegalArgumentException if {@code count} is negative */ public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); } /** * Causes the current thread to wait until the latch has counted down to * zero, unless the thread is {@linkplain Thread#interrupt interrupted}. * * <p>If the current count is zero then this method returns immediately. * * <p>If the current count is greater than zero then the current * thread becomes disabled for thread scheduling purposes and lies * dormant until one of two things happen: * <ul> * <li>The count reaches zero due to invocations of the * {@link #countDown} method; or * <li>Some other thread {@linkplain Thread#interrupt interrupts} * the current thread. * </ul> * * <p>If the current thread: * <ul> * <li>has its interrupted status set on entry to this method; or * <li>is {@linkplain Thread#interrupt interrupted} while waiting, * </ul> * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * * @throws InterruptedException if the current thread is interrupted * while waiting */ public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } /** * Causes the current thread to wait until the latch has counted down to * zero, unless the thread is {@linkplain Thread#interrupt interrupted}, * or the specified waiting time elapses. * * <p>If the current count is zero then this method returns immediately * with the value {@code true}. * * <p>If the current count is greater than zero then the current * thread becomes disabled for thread scheduling purposes and lies * dormant until one of three things happen: * <ul> * <li>The count reaches zero due to invocations of the * {@link #countDown} method; or * <li>Some other thread {@linkplain Thread#interrupt interrupts} * the current thread; or * <li>The specified waiting time elapses. * </ul> * * <p>If the count reaches zero then the method returns with the * value {@code true}. * * <p>If the current thread: * <ul> * <li>has its interrupted status set on entry to this method; or * <li>is {@linkplain Thread#interrupt interrupted} while waiting, * </ul> * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * * <p>If the specified waiting time elapses then the value {@code false} * is returned. If the time is less than or equal to zero, the method * will not wait at all. * * @param timeout the maximum time to wait * @param unit the time unit of the {@code timeout} argument * @return {@code true} if the count reached zero and {@code false} * if the waiting time elapsed before the count reached zero * @throws InterruptedException if the current thread is interrupted * while waiting */ public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } /** * Decrements the count of the latch, releasing all waiting threads if * the count reaches zero. * * <p>If the current count is greater than zero then it is decremented. * If the new count is zero then all waiting threads are re-enabled for * thread scheduling purposes. * * <p>If the current count equals zero then nothing happens. */ public void countDown() { sync.releaseShared(1); } /** * Returns the current count. * * <p>This method is typically used for debugging and testing purposes. * * @return the current count */ public long getCount() { return sync.getCount(); } /** * Returns a string identifying this latch, as well as its state. * The state, in brackets, includes the String {@code "Count ="} * followed by the current count. * * @return a string identifying this latch, as well as its state */ public String toString() { return super.toString() + "[Count = " + sync.getCount() + "]"; } }
核心方法:
CountDownLatch(int count) int值参数的构造函数,count代表倒数计数。
countDown() 执行计数减操作(每次调用释放一个锁)。
await() 等待指令,调用CountDownLatch.await()之后当前线程会进入等待状态,直至预设的锁全部释放为止。
下面我们以一个经典的场景做示例,假设一项工作需要三个工作做好准备工作之后才能开始,示例如下:
@Test public void work() { int needCount = 3; CountDownLatch ready = new CountDownLatch(needCount); for (int i = 0; i < needCount; i++) { Thread thread = new Thread(new Worker(i, ready)); thread.start(); } try { ready.await(); log.info("Workers is ready begin next Job ..."); } catch (InterruptedException e) { e.printStackTrace(); } } @Slf4j static class Worker implements Runnable { int id; CountDownLatch ready; Worker(int id, CountDownLatch ready) { this.id = id; this.ready = ready; } @Override public void run() { try { Random random = new Random(); int cost = random.nextInt(10000); log.info("Worker{} begin prepare job ...", id); Thread.sleep(cost); log.info("Worker{} is ready and cost {}ms ...", id, cost); ready.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果如下:
2019-07-16 14:52:27.376 [Thread-1] INFO com.longc.demo.study.thread.TestCountDownLatch$Worker - Worker1 begin prepare job ... 2019-07-16 14:52:27.376 [Thread-2] INFO com.longc.demo.study.thread.TestCountDownLatch$Worker - Worker2 begin prepare job ... 2019-07-16 14:52:27.376 [Thread-0] INFO com.longc.demo.study.thread.TestCountDownLatch$Worker - Worker0 begin prepare job ... 2019-07-16 14:52:30.609 [Thread-0] INFO com.longc.demo.study.thread.TestCountDownLatch$Worker - Worker0 is ready and cost 3225ms ... 2019-07-16 14:52:33.750 [Thread-1] INFO com.longc.demo.study.thread.TestCountDownLatch$Worker - Worker1 is ready and cost 6362ms ... 2019-07-16 14:52:34.724 [Thread-2] INFO com.longc.demo.study.thread.TestCountDownLatch$Worker - Worker2 is ready and cost 7337ms ... 2019-07-16 14:52:34.724 [main] INFO com.longc.demo.study.thread.TestCountDownLatch - Workers is ready begin next Job ...
根据运行结果可知主线程调用CountDownLatch.await()之后就会进入等待状态,直至线程0、线程1、线程2分别完成自己的工作后计数器减(释放一个锁)后,所有锁全部释放才开始执行后续的操作。此种方式相对灵活,不需要获取线程对象即可操作,对于使用线程池管理线程的我们来说是很方便的。这里有什么不懂的地方可以仔细学习一下AQS相关知识。
(3)CyclicBarrier字面意义是循环屏障,其原理大致为让一组线程到达同步点后被阻塞,先到先阻塞,直到指定数量的线程到达同步点后解除屏障。此时所有被阻塞的线程才会继续工作。先把源码贴出来:
/* * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ /* * * * * * * Written by Doug Lea with assistance from members of JCP JSR-166 * Expert Group and released to the public domain, as explained at * http://creativecommons.org/publicdomain/zero/1.0/ */ package java.util.concurrent; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * A synchronization aid that allows a set of threads to all wait for * each other to reach a common barrier point. CyclicBarriers are * useful in programs involving a fixed sized party of threads that * must occasionally wait for each other. The barrier is called * <em>cyclic</em> because it can be re-used after the waiting threads * are released. * * <p>A {@code CyclicBarrier} supports an optional {@link Runnable} command * that is run once per barrier point, after the last thread in the party * arrives, but before any threads are released. * This <em>barrier action</em> is useful * for updating shared-state before any of the parties continue. * * <p><b>Sample usage:</b> Here is an example of using a barrier in a * parallel decomposition design: * * <pre> {@code * class Solver { * final int N; * final float[][] data; * final CyclicBarrier barrier; * * class Worker implements Runnable { * int myRow; * Worker(int row) { myRow = row; } * public void run() { * while (!done()) { * processRow(myRow); * * try { * barrier.await(); * } catch (InterruptedException ex) { * return; * } catch (BrokenBarrierException ex) { * return; * } * } * } * } * * public Solver(float[][] matrix) { * data = matrix; * N = matrix.length; * Runnable barrierAction = * new Runnable() { public void run() { mergeRows(...); }}; * barrier = new CyclicBarrier(N, barrierAction); * * List<Thread> threads = new ArrayList<Thread>(N); * for (int i = 0; i < N; i++) { * Thread thread = new Thread(new Worker(i)); * threads.add(thread); * thread.start(); * } * * // wait until done * for (Thread thread : threads) * thread.join(); * } * }}</pre> * * Here, each worker thread processes a row of the matrix then waits at the * barrier until all rows have been processed. When all rows are processed * the supplied {@link Runnable} barrier action is executed and merges the * rows. If the merger * determines that a solution has been found then {@code done()} will return * {@code true} and each worker will terminate. * * <p>If the barrier action does not rely on the parties being suspended when * it is executed, then any of the threads in the party could execute that * action when it is released. To facilitate this, each invocation of * {@link #await} returns the arrival index of that thread at the barrier. * You can then choose which thread should execute the barrier action, for * example: * <pre> {@code * if (barrier.await() == 0) { * // log the completion of this iteration * }}</pre> * * <p>The {@code CyclicBarrier} uses an all-or-none breakage model * for failed synchronization attempts: If a thread leaves a barrier * point prematurely because of interruption, failure, or timeout, all * other threads waiting at that barrier point will also leave * abnormally via {@link BrokenBarrierException} (or * {@link InterruptedException} if they too were interrupted at about * the same time). * * <p>Memory consistency effects: Actions in a thread prior to calling * {@code await()} * <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a> * actions that are part of the barrier action, which in turn * <i>happen-before</i> actions following a successful return from the * corresponding {@code await()} in other threads. * * @since 1.5 * @see CountDownLatch * * @author Doug Lea */ public class CyclicBarrier { /** * Each use of the barrier is represented as a generation instance. * The generation changes whenever the barrier is tripped, or * is reset. There can be many generations associated with threads * using the barrier - due to the non-deterministic way the lock * may be allocated to waiting threads - but only one of these * can be active at a time (the one to which {@code count} applies) * and all the rest are either broken or tripped. * There need not be an active generation if there has been a break * but no subsequent reset. */ private static class Generation { boolean broken = false; } /** 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 */ private final Runnable barrierCommand; /** The current generation */ private Generation generation = new Generation(); /** * Number of parties still waiting. Counts down from parties to 0 * on each generation. It is reset to parties on each new * generation or when broken. */ private int count; /** * Updates state on barrier trip and wakes up everyone. * Called only while holding lock. */ private void nextGeneration() { // signal completion of last generation trip.signalAll(); // set up next generation count = parties; generation = new Generation(); } /** * Sets current barrier generation as broken and wakes up everyone. * Called only while holding lock. */ private void breakBarrier() { generation.broken = true; count = parties; trip.signalAll(); } /** * Main barrier code, covering the various policies. */ 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()) { breakBarrier(); throw new InterruptedException(); } int index = --count; if (index == 0) { // tripped boolean ranAction = false; try { final Runnable command = barrierCommand; if (command != null) command.run(); 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) { if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { // We're about to finish waiting even if we had not // been interrupted, so this interrupt is deemed to // "belong" to subsequent execution. Thread.currentThread().interrupt(); } } if (g.broken) throw new BrokenBarrierException(); if (g != generation) return index; if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } } /** * Creates a new {@code CyclicBarrier} that will trip when the * given number of parties (threads) are waiting upon it, and which * will execute the given barrier action when the barrier is tripped, * performed by the last thread entering the barrier. * * @param parties the number of threads that must invoke {@link #await} * before the barrier is tripped * @param barrierAction the command to execute when the barrier is * tripped, or {@code null} if there is no action * @throws IllegalArgumentException if {@code parties} is less than 1 */ public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; this.barrierCommand = barrierAction; } /** * Creates a new {@code CyclicBarrier} that will trip when the * given number of parties (threads) are waiting upon it, and * does not perform a predefined action when the barrier is tripped. * * @param parties the number of threads that must invoke {@link #await} * before the barrier is tripped * @throws IllegalArgumentException if {@code parties} is less than 1 */ public CyclicBarrier(int parties) { this(parties, null); } /** * Returns the number of parties required to trip this barrier. * * @return the number of parties required to trip this barrier */ public int getParties() { return parties; } /** * Waits until all {@linkplain #getParties parties} have invoked * {@code await} on this barrier. * * <p>If the current thread is not the last to arrive then it is * disabled for thread scheduling purposes and lies dormant until * one of the following things happens: * <ul> * <li>The last thread arrives; or * <li>Some other thread {@linkplain Thread#interrupt interrupts} * the current thread; or * <li>Some other thread {@linkplain Thread#interrupt interrupts} * one of the other waiting threads; or * <li>Some other thread times out while waiting for barrier; or * <li>Some other thread invokes {@link #reset} on this barrier. * </ul> * * <p>If the current thread: * <ul> * <li>has its interrupted status set on entry to this method; or * <li>is {@linkplain Thread#interrupt interrupted} while waiting * </ul> * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * * <p>If the barrier is {@link #reset} while any thread is waiting, * or if the barrier {@linkplain #isBroken is broken} when * {@code await} is invoked, or while any thread is waiting, then * {@link BrokenBarrierException} is thrown. * * <p>If any thread is {@linkplain Thread#interrupt interrupted} while waiting, * then all other waiting threads will throw * {@link BrokenBarrierException} and the barrier is placed in the broken * state. * * <p>If the current thread is the last thread to arrive, and a * non-null barrier action was supplied in the constructor, then the * current thread runs the action before allowing the other threads to * continue. * If an exception occurs during the barrier action then that exception * will be propagated in the current thread and the barrier is placed in * the broken state. * * @return the arrival index of the current thread, where index * {@code getParties() - 1} indicates the first * to arrive and zero indicates the last to arrive * @throws InterruptedException if the current thread was interrupted * while waiting * @throws BrokenBarrierException if <em>another</em> thread was * interrupted or timed out while the current thread was * waiting, or the barrier was reset, or the barrier was * broken when {@code await} was called, or the barrier * action (if present) failed due to an exception */ public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen } } /** * Waits until all {@linkplain #getParties parties} have invoked * {@code await} on this barrier, or the specified waiting time elapses. * * <p>If the current thread is not the last to arrive then it is * disabled for thread scheduling purposes and lies dormant until * one of the following things happens: * <ul> * <li>The last thread arrives; or * <li>The specified timeout elapses; or * <li>Some other thread {@linkplain Thread#interrupt interrupts} * the current thread; or * <li>Some other thread {@linkplain Thread#interrupt interrupts} * one of the other waiting threads; or * <li>Some other thread times out while waiting for barrier; or * <li>Some other thread invokes {@link #reset} on this barrier. * </ul> * * <p>If the current thread: * <ul> * <li>has its interrupted status set on entry to this method; or * <li>is {@linkplain Thread#interrupt interrupted} while waiting * </ul> * then {@link InterruptedException} is thrown and the current thread's * interrupted status is cleared. * * <p>If the specified waiting time elapses then {@link TimeoutException} * is thrown. If the time is less than or equal to zero, the * method will not wait at all. * * <p>If the barrier is {@link #reset} while any thread is waiting, * or if the barrier {@linkplain #isBroken is broken} when * {@code await} is invoked, or while any thread is waiting, then * {@link BrokenBarrierException} is thrown. * * <p>If any thread is {@linkplain Thread#interrupt interrupted} while * waiting, then all other waiting threads will throw {@link * BrokenBarrierException} and the barrier is placed in the broken * state. * * <p>If the current thread is the last thread to arrive, and a * non-null barrier action was supplied in the constructor, then the * current thread runs the action before allowing the other threads to * continue. * If an exception occurs during the barrier action then that exception * will be propagated in the current thread and the barrier is placed in * the broken state. * * @param timeout the time to wait for the barrier * @param unit the time unit of the timeout parameter * @return the arrival index of the current thread, where index * {@code getParties() - 1} indicates the first * to arrive and zero indicates the last to arrive * @throws InterruptedException if the current thread was interrupted * while waiting * @throws TimeoutException if the specified timeout elapses. * In this case the barrier will be broken. * @throws BrokenBarrierException if <em>another</em> thread was * interrupted or timed out while the current thread was * waiting, or the barrier was reset, or the barrier was broken * when {@code await} was called, or the barrier action (if * present) failed due to an exception */ public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException { return dowait(true, unit.toNanos(timeout)); } /** * Queries if this barrier is in a broken state. * * @return {@code true} if one or more parties broke out of this * barrier due to interruption or timeout since * construction or the last reset, or a barrier action * failed due to an exception; {@code false} otherwise. */ public boolean isBroken() { final ReentrantLock lock = this.lock; lock.lock(); try { return generation.broken; } finally { lock.unlock(); } } /** * Resets the barrier to its initial state. If any parties are * currently waiting at the barrier, they will return with a * {@link BrokenBarrierException}. Note that resets <em>after</em> * a breakage has occurred for other reasons can be complicated to * carry out; threads need to re-synchronize in some other way, * and choose one to perform the reset. It may be preferable to * instead create a new barrier for subsequent use. */ public void reset() { final ReentrantLock lock = this.lock; lock.lock(); try { breakBarrier(); // break the current generation nextGeneration(); // start a new generation } finally { lock.unlock(); } } /** * Returns the number of parties currently waiting at the barrier. * This method is primarily useful for debugging and assertions. * * @return the number of parties currently blocked in {@link #await} */ public int getNumberWaiting() { final ReentrantLock lock = this.lock; lock.lock(); try { return parties - count; } finally { lock.unlock(); } } }
实现原理:在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用CyclicBarrier的await方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从await方法返回,再从CyclicBarrier的await方法中返回。
下面我们再以一个经典的场景作为示例,运动员赛跑,需要所有运动员均准备好后裁判发令出发,运动员接收到指令后出发,代码如下:
public static void main(String[] args) { int runnerCount = 4; CyclicBarrier barrier = new CyclicBarrier(runnerCount); for (int i = 0; i < runnerCount; i++) new Runner(barrier).start(); } static class Runner extends Thread { private CyclicBarrier cyclicBarrier; Runner(CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; log.info("运动员{} 进入场地 ...", getName()); } @Override public void run() { try { Random random = new Random(); int sleep = random.nextInt(10000); Thread.sleep(sleep); log.info("运动员{}准备完毕,耗时{}ms,等待起跑指令...", getName(), sleep); cyclicBarrier.await(); log.info("运动员{}开始出发...", getName()); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } }
运行结果如下:
2019-07-16 15:34:39.643 [main] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-0 进入场地 ... 2019-07-16 15:34:39.649 [main] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-1 进入场地 ... 2019-07-16 15:34:39.649 [main] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-2 进入场地 ... 2019-07-16 15:34:39.649 [main] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-3 进入场地 ... 2019-07-16 15:34:41.519 [Thread-2] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-2准备完毕,耗时1866ms,等待起跑指令... 2019-07-16 15:34:44.264 [Thread-3] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-3准备完毕,耗时4611ms,等待起跑指令... 2019-07-16 15:34:44.816 [Thread-1] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-1准备完毕,耗时5162ms,等待起跑指令... 2019-07-16 15:34:49.454 [Thread-0] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-0准备完毕,耗时9801ms,等待起跑指令... 2019-07-16 15:34:49.454 [Thread-0] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-0开始出发... 2019-07-16 15:34:49.454 [Thread-2] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-2开始出发... 2019-07-16 15:34:49.454 [Thread-3] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-3开始出发... 2019-07-16 15:34:49.454 [Thread-1] INFO com.longc.demo.study.thread.TestCyclicBarrier - 运动员Thread-1开始出发...
简单做个总结:
(1)使用场景不同
CyclicBarrier适用于一组线程之间的相互等待,而CountDownLatch、join适用于某线程或某组线程等待另一组线程的场景。
CountDownLatch两个方法配合使用也可以实现CyclicBarrier的功能的,即在线程内调用countDown() 和 await()
(2)实现方式不同
CountDownLatch是通过AQS共享锁实现的
CyclicBarrier核心是通过ReentranLock非公平锁(独占锁)实现的
join则是利用自旋Object.wait()实现。
(3)使用规则不同
CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。