Java多线程系列--【JUC锁09】-CountDownLatch原理和示例

参考:http://www.cnblogs.com/skywang12345/p/3533887.html

概要

前面对"独占锁"和"共享锁"有了个大致的了解;本章,我们对CountDownLatch进行学习。和ReadWriteLock.ReadLock一样,CountDownLatch的本质也是一个"共享锁"。本章的内容包括:
CountDownLatch简介
CountDownLatch数据结构

CountDownLatch源码分析(基于JDK1.7.0_40)
CountDownLatch示例

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3533887.html

 

CountDownLatch简介

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

 

CountDownLatch和CyclicBarrier的区别
(01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
(02) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。
关于CyclicBarrier的原理,后面一章再来学习。


CountDownLatch函数列表

复制代码
CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch。

// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()
复制代码

 

CountDownLatch数据结构

CountDownLatch的UML类图如下:

Java多线程系列--【JUC锁09】-CountDownLatch原理和示例_第1张图片

CountDownLatch的数据结构很简单,它是通过"共享锁"实现的。它包含了sync对象,sync是Sync类型。Sync是实例类,它继承于AQS。

 

CountDownLatch源码分析(基于JDK1.7.0_40)

CountDownLatch完整源码(基于JDK1.7.0_40)

复制代码
  1 /*
  2  * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  3  *
  4  *
  5  *
  6  *
  7  *
  8  *
  9  *
 10  *
 11  *
 12  *
 13  *
 14  *
 15  *
 16  *
 17  *
 18  *
 19  *
 20  *
 21  *
 22  *
 23  */
 24 
 25 /*
 26  *
 27  *
 28  *
 29  *
 30  *
 31  * Written by Doug Lea with assistance from members of JCP JSR-166
 32  * Expert Group and released to the public domain, as explained at
 33  * http://creativecommons.org/publicdomain/zero/1.0/
 34  */
 35 
 36 package java.util.concurrent;
 37 import java.util.concurrent.locks.*;
 38 import java.util.concurrent.atomic.*;
 39 
 40 /**
 41  * A synchronization aid that allows one or more threads to wait until
 42  * a set of operations being performed in other threads completes.
 43  *
 44  * 

A {@code CountDownLatch} is initialized with a given count. 45 * The {@link #await await} methods block until the current count reaches 46 * zero due to invocations of the {@link #countDown} method, after which 47 * all waiting threads are released and any subsequent invocations of 48 * {@link #await await} return immediately. This is a one-shot phenomenon 49 * -- the count cannot be reset. If you need a version that resets the 50 * count, consider using a {@link CyclicBarrier}. 51 * 52 *

A {@code CountDownLatch} is a versatile synchronization tool 53 * and can be used for a number of purposes. A 54 * {@code CountDownLatch} initialized with a count of one serves as a 55 * simple on/off latch, or gate: all threads invoking {@link #await await} 56 * wait at the gate until it is opened by a thread invoking {@link 57 * #countDown}. A {@code CountDownLatch} initialized to N 58 * can be used to make one thread wait until N threads have 59 * completed some action, or some action has been completed N times. 60 * 61 *

A useful property of a {@code CountDownLatch} is that it 62 * doesn't require that threads calling {@code countDown} wait for 63 * the count to reach zero before proceeding, it simply prevents any 64 * thread from proceeding past an {@link #await await} until all 65 * threads could pass. 66 * 67 *

Sample usage: Here is a pair of classes in which a group 68 * of worker threads use two countdown latches: 69 *

    70 *
  • The first is a start signal that prevents any worker from proceeding 71 * until the driver is ready for them to proceed; 72 *
  • The second is a completion signal that allows the driver to wait 73 * until all workers have completed. 74 *
75 * 76 *
 77  * class Driver { // ...
 78  *   void main() throws InterruptedException {
 79  *     CountDownLatch startSignal = new CountDownLatch(1);
 80  *     CountDownLatch doneSignal = new CountDownLatch(N);
 81  *
 82  *     for (int i = 0; i < N; ++i) // create and start threads
 83  *       new Thread(new Worker(startSignal, doneSignal)).start();
 84  *
 85  *     doSomethingElse();            // don't let run yet
 86  *     startSignal.countDown();      // let all threads proceed
 87  *     doSomethingElse();
 88  *     doneSignal.await();           // wait for all to finish
 89  *   }
 90  * }
 91  *
 92  * class Worker implements Runnable {
 93  *   private final CountDownLatch startSignal;
 94  *   private final CountDownLatch doneSignal;
 95  *   Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
 96  *      this.startSignal = startSignal;
 97  *      this.doneSignal = doneSignal;
 98  *   }
 99  *   public void run() {
100  *      try {
101  *        startSignal.await();
102  *        doWork();
103  *        doneSignal.countDown();
104  *      } catch (InterruptedException ex) {} // return;
105  *   }
106  *
107  *   void doWork() { ... }
108  * }
109  *
110  * 
111 * 112 *

Another typical usage would be to divide a problem into N parts, 113 * describe each part with a Runnable that executes that portion and 114 * counts down on the latch, and queue all the Runnables to an 115 * Executor. When all sub-parts are complete, the coordinating thread 116 * will be able to pass through await. (When threads must repeatedly 117 * count down in this way, instead use a {@link CyclicBarrier}.) 118 * 119 *

120  * class Driver2 { // ...
121  *   void main() throws InterruptedException {
122  *     CountDownLatch doneSignal = new CountDownLatch(N);
123  *     Executor e = ...
124  *
125  *     for (int i = 0; i < N; ++i) // create and start threads
126  *       e.execute(new WorkerRunnable(doneSignal, i));
127  *
128  *     doneSignal.await();           // wait for all to finish
129  *   }
130  * }
131  *
132  * class WorkerRunnable implements Runnable {
133  *   private final CountDownLatch doneSignal;
134  *   private final int i;
135  *   WorkerRunnable(CountDownLatch doneSignal, int i) {
136  *      this.doneSignal = doneSignal;
137  *      this.i = i;
138  *   }
139  *   public void run() {
140  *      try {
141  *        doWork(i);
142  *        doneSignal.countDown();
143  *      } catch (InterruptedException ex) {} // return;
144  *   }
145  *
146  *   void doWork() { ... }
147  * }
148  *
149  * 
150 * 151 *

Memory consistency effects: Until the count reaches 152 * zero, actions in a thread prior to calling 153 * {@code countDown()} 154 * happen-before 155 * actions following a successful return from a corresponding 156 * {@code await()} in another thread. 157 * 158 * @since 1.5 159 * @author Doug Lea 160 */ 161 public class CountDownLatch { 162 /** 163 * Synchronization control For CountDownLatch. 164 * Uses AQS state to represent count. 165 */ 166 private static final class Sync extends AbstractQueuedSynchronizer { 167 private static final long serialVersionUID = 4982264981922014374L; 168 169 Sync(int count) { 170 setState(count); 171 } 172 173 int getCount() { 174 return getState(); 175 } 176 177 protected int tryAcquireShared(int acquires) { 178 return (getState() == 0) ? 1 : -1; 179 } 180 181 protected boolean tryReleaseShared(int releases) { 182 // Decrement count; signal when transition to zero 183 for (;;) { 184 int c = getState(); 185 if (c == 0) 186 return false; 187 int nextc = c-1; 188 if (compareAndSetState(c, nextc)) 189 return nextc == 0; 190 } 191 } 192 } 193 194 private final Sync sync; 195 196 /** 197 * Constructs a {@code CountDownLatch} initialized with the given count. 198 * 199 * @param count the number of times {@link #countDown} must be invoked 200 * before threads can pass through {@link #await} 201 * @throws IllegalArgumentException if {@code count} is negative 202 */ 203 public CountDownLatch(int count) { 204 if (count < 0) throw new IllegalArgumentException("count < 0"); 205 this.sync = new Sync(count); 206 } 207 208 /** 209 * Causes the current thread to wait until the latch has counted down to 210 * zero, unless the thread is {@linkplain Thread#interrupt interrupted}. 211 * 212 *

If the current count is zero then this method returns immediately. 213 * 214 *

If the current count is greater than zero then the current 215 * thread becomes disabled for thread scheduling purposes and lies 216 * dormant until one of two things happen: 217 *

    218 *
  • The count reaches zero due to invocations of the 219 * {@link #countDown} method; or 220 *
  • Some other thread {@linkplain Thread#interrupt interrupts} 221 * the current thread. 222 *
223 * 224 *

If the current thread: 225 *

    226 *
  • has its interrupted status set on entry to this method; or 227 *
  • is {@linkplain Thread#interrupt interrupted} while waiting, 228 *
229 * then {@link InterruptedException} is thrown and the current thread's 230 * interrupted status is cleared. 231 * 232 * @throws InterruptedException if the current thread is interrupted 233 * while waiting 234 */ 235 public void await() throws InterruptedException { 236 sync.acquireSharedInterruptibly(1); 237 } 238 239 /** 240 * Causes the current thread to wait until the latch has counted down to 241 * zero, unless the thread is {@linkplain Thread#interrupt interrupted}, 242 * or the specified waiting time elapses. 243 * 244 *

If the current count is zero then this method returns immediately 245 * with the value {@code true}. 246 * 247 *

If the current count is greater than zero then the current 248 * thread becomes disabled for thread scheduling purposes and lies 249 * dormant until one of three things happen: 250 *

    251 *
  • The count reaches zero due to invocations of the 252 * {@link #countDown} method; or 253 *
  • Some other thread {@linkplain Thread#interrupt interrupts} 254 * the current thread; or 255 *
  • The specified waiting time elapses. 256 *
257 * 258 *

If the count reaches zero then the method returns with the 259 * value {@code true}. 260 * 261 *

If the current thread: 262 *

    263 *
  • has its interrupted status set on entry to this method; or 264 *
  • is {@linkplain Thread#interrupt interrupted} while waiting, 265 *
266 * then {@link InterruptedException} is thrown and the current thread's 267 * interrupted status is cleared. 268 * 269 *

If the specified waiting time elapses then the value {@code false} 270 * is returned. If the time is less than or equal to zero, the method 271 * will not wait at all. 272 * 273 * @param timeout the maximum time to wait 274 * @param unit the time unit of the {@code timeout} argument 275 * @return {@code true} if the count reached zero and {@code false} 276 * if the waiting time elapsed before the count reached zero 277 * @throws InterruptedException if the current thread is interrupted 278 * while waiting 279 */ 280 public boolean await(long timeout, TimeUnit unit) 281 throws InterruptedException { 282 return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); 283 } 284 285 /** 286 * Decrements the count of the latch, releasing all waiting threads if 287 * the count reaches zero. 288 * 289 *

If the current count is greater than zero then it is decremented. 290 * If the new count is zero then all waiting threads are re-enabled for 291 * thread scheduling purposes. 292 * 293 *

If the current count equals zero then nothing happens. 294 */ 295 public void countDown() { 296 sync.releaseShared(1); 297 } 298 299 /** 300 * Returns the current count. 301 * 302 *

This method is typically used for debugging and testing purposes. 303 * 304 * @return the current count 305 */ 306 public long getCount() { 307 return sync.getCount(); 308 } 309 310 /** 311 * Returns a string identifying this latch, as well as its state. 312 * The state, in brackets, includes the String {@code "Count ="} 313 * followed by the current count. 314 * 315 * @return a string identifying this latch, as well as its state 316 */ 317 public String toString() { 318 return super.toString() + "[Count = " + sync.getCount() + "]"; 319 } 320 }

复制代码

CountDownLatch是通过“共享锁”实现的。下面,我们分析CountDownLatch中3个核心函数: CountDownLatch(int count), await(), countDown()。

 

1. CountDownLatch(int count)

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

说明:该函数是创建一个Sync对象,而Sync是继承于AQS类。Sync构造函数如下:

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

 

setState()在AQS中实现,源码如下:

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

说明:在AQS中,state是一个private volatile long类型的对象。对于CountDownLatch而言,state表示的”锁计数器“。CountDownLatch中的getCount()最终是调用AQS中的getState(),返回的state对象,即”锁计数器“。

 

2. await()

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

说明:该函数实际上是调用的AQS的acquireSharedInterruptibly(1);

AQS中的acquireSharedInterruptibly()的源码如下:

复制代码
public final void acquireSharedInterruptibly(long arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
复制代码

说明:acquireSharedInterruptibly()的作用是获取共享锁。
如果当前线程是中断状态,则抛出异常InterruptedException。否则,调用tryAcquireShared(arg)尝试获取共享锁;尝试成功则返回,否则就调用doAcquireSharedInterruptibly()。doAcquireSharedInterruptibly()会使当前线程一直等待,直到当前线程获取到共享锁(或被中断)才返回。

tryAcquireShared()在CountDownLatch.java中被重写,它的源码如下:

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

说明:tryAcquireShared()的作用是尝试获取共享锁。
如果"锁计数器=0",即锁是可获取状态,则返回1;否则,锁是不可获取状态,则返回-1。

复制代码
private void doAcquireSharedInterruptibly(long arg)
    throws InterruptedException {
    // 创建"当前线程"的Node节点,且Node中记录的锁是"共享锁"类型;并将该节点添加到CLH队列末尾。
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 获取上一个节点。
            // 如果上一节点是CLH队列的表头,则"尝试获取共享锁"。
            final Node p = node.predecessor();
            if (p == head) {
                long r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // (上一节点不是CLH队列的表头) 当前线程一直等待,直到获取到共享锁。
            // 如果线程在等待过程中被中断过,则再次中断该线程(还原之前的中断状态)。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

说明
(01) addWaiter(Node.SHARED)的作用是,创建”当前线程“的Node节点,且Node中记录的锁的类型是”共享锁“(Node.SHARED);并将该节点添加到CLH队列末尾。关于Node和CLH在"Java多线程系列--“JUC锁”03之 公平锁(一)"已经详细介绍过,这里就不再重复说明了。
(02) node.predecessor()的作用是,获取上一个节点。如果上一节点是CLH队列的表头,则”尝试获取共享锁“。
(03) shouldParkAfterFailedAcquire()的作用和它的名称一样,如果在尝试获取锁失败之后,线程应该等待,则返回true;否则,返回false。
(04) 当shouldParkAfterFailedAcquire()返回ture时,则调用parkAndCheckInterrupt(),当前线程会进入等待状态,直到获取到共享锁才继续运行。
doAcquireSharedInterruptibly()中的shouldParkAfterFailedAcquire(), parkAndCheckInterrupt等函数在"Java多线程系列--“JUC锁”03之 公平锁(一)"中介绍过,这里也就不再详细说明了。

 

3. countDown()

public void countDown() {
    sync.releaseShared(1);
}

说明:该函数实际上调用releaseShared(1)释放共享锁。

releaseShared()在AQS中实现,源码如下:

复制代码
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
复制代码

说明:releaseShared()的目的是让当前线程释放它所持有的共享锁。
它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doReleaseShared()去释放共享锁。

tryReleaseShared()在CountDownLatch.java中被重写,源码如下:

复制代码
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        // 获取“锁计数器”的状态
        int c = getState();
        if (c == 0)
            return false;
        // “锁计数器”-1
        int nextc = c-1;
        // 通过CAS函数进行赋值。
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
复制代码

说明:tryReleaseShared()的作用是释放共享锁,将“锁计数器”的值-1。

 

总结:CountDownLatch是通过“共享锁”实现的。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count给线程同时获取。当某线程调用该CountDownLatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行!

以上,就是CountDownLatch的实现原理。

 

CountDownLatch的使用示例

下面通过CountDownLatch实现:"主线程"等待"5个子线程"全部都完成"指定的工作(休眠1000ms)"之后,再继续运行。

复制代码
 1 import java.util.concurrent.CountDownLatch;
 2 import java.util.concurrent.CyclicBarrier;
 3 
 4 public class CountDownLatchTest1 {
 5 
 6     private static int LATCH_SIZE = 5;
 7     private static CountDownLatch doneSignal;
 8     public static void main(String[] args) {
 9 
10         try {
11             doneSignal = new CountDownLatch(LATCH_SIZE);
12 
13             // 新建5个任务
14             for(int i=0; i)
15                 new InnerThread().start();
16 
17             System.out.println("main await begin.");
18             // "主线程"等待线程池中5个任务的完成
19             doneSignal.await();
20 
21             System.out.println("main await finished.");
22         } catch (InterruptedException e) {
23             e.printStackTrace();
24         }
25     }
26 
27     static class InnerThread extends Thread{
28         public void run() {
29             try {
30                 Thread.sleep(1000);
31                 System.out.println(Thread.currentThread().getName() + " sleep 1000ms.");
32                 // 将CountDownLatch的数值减1
33                 doneSignal.countDown();
34             } catch (InterruptedException e) {
35                 e.printStackTrace();
36             }
37         }
38     }
39 }
复制代码

运行结果

复制代码
main await begin.
Thread-0 sleep 1000ms.
Thread-2 sleep 1000ms.
Thread-1 sleep 1000ms.
Thread-4 sleep 1000ms.
Thread-3 sleep 1000ms.
main await finished.
复制代码

结果说明:主线程通过doneSignal.await()等待其它线程将doneSignal递减至0。其它的5个InnerThread线程,每一个都通过doneSignal.countDown()将doneSignal的值减1;当doneSignal为0时,main被唤醒后继续执行。

  


 

概要

前面对"独占锁"和"共享锁"有了个大致的了解;本章,我们对CountDownLatch进行学习。和ReadWriteLock.ReadLock一样,CountDownLatch的本质也是一个"共享锁"。本章的内容包括:
CountDownLatch简介
CountDownLatch数据结构

CountDownLatch源码分析(基于JDK1.7.0_40)
CountDownLatch示例

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3533887.html

 

CountDownLatch简介

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

 

CountDownLatch和CyclicBarrier的区别
(01) CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。
(02) CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。
关于CyclicBarrier的原理,后面一章再来学习。


CountDownLatch函数列表

复制代码
CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch。

// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
void await()
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
boolean await(long timeout, TimeUnit unit)
// 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
void countDown()
// 返回当前计数。
long getCount()
// 返回标识此锁存器及其状态的字符串。
String toString()
复制代码

 

CountDownLatch数据结构

CountDownLatch的UML类图如下:

Java多线程系列--【JUC锁09】-CountDownLatch原理和示例_第2张图片

CountDownLatch的数据结构很简单,它是通过"共享锁"实现的。它包含了sync对象,sync是Sync类型。Sync是实例类,它继承于AQS。

 

CountDownLatch源码分析(基于JDK1.7.0_40)

CountDownLatch完整源码(基于JDK1.7.0_40)

复制代码
  1 /*
  2  * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  3  *
  4  *
  5  *
  6  *
  7  *
  8  *
  9  *
 10  *
 11  *
 12  *
 13  *
 14  *
 15  *
 16  *
 17  *
 18  *
 19  *
 20  *
 21  *
 22  *
 23  */
 24 
 25 /*
 26  *
 27  *
 28  *
 29  *
 30  *
 31  * Written by Doug Lea with assistance from members of JCP JSR-166
 32  * Expert Group and released to the public domain, as explained at
 33  * http://creativecommons.org/publicdomain/zero/1.0/
 34  */
 35 
 36 package java.util.concurrent;
 37 import java.util.concurrent.locks.*;
 38 import java.util.concurrent.atomic.*;
 39 
 40 /**
 41  * A synchronization aid that allows one or more threads to wait until
 42  * a set of operations being performed in other threads completes.
 43  *
 44  * 

A {@code CountDownLatch} is initialized with a given count. 45 * The {@link #await await} methods block until the current count reaches 46 * zero due to invocations of the {@link #countDown} method, after which 47 * all waiting threads are released and any subsequent invocations of 48 * {@link #await await} return immediately. This is a one-shot phenomenon 49 * -- the count cannot be reset. If you need a version that resets the 50 * count, consider using a {@link CyclicBarrier}. 51 * 52 *

A {@code CountDownLatch} is a versatile synchronization tool 53 * and can be used for a number of purposes. A 54 * {@code CountDownLatch} initialized with a count of one serves as a 55 * simple on/off latch, or gate: all threads invoking {@link #await await} 56 * wait at the gate until it is opened by a thread invoking {@link 57 * #countDown}. A {@code CountDownLatch} initialized to N 58 * can be used to make one thread wait until N threads have 59 * completed some action, or some action has been completed N times. 60 * 61 *

A useful property of a {@code CountDownLatch} is that it 62 * doesn't require that threads calling {@code countDown} wait for 63 * the count to reach zero before proceeding, it simply prevents any 64 * thread from proceeding past an {@link #await await} until all 65 * threads could pass. 66 * 67 *

Sample usage: Here is a pair of classes in which a group 68 * of worker threads use two countdown latches: 69 *

    70 *
  • The first is a start signal that prevents any worker from proceeding 71 * until the driver is ready for them to proceed; 72 *
  • The second is a completion signal that allows the driver to wait 73 * until all workers have completed. 74 *
75 * 76 *
 77  * class Driver { // ...
 78  *   void main() throws InterruptedException {
 79  *     CountDownLatch startSignal = new CountDownLatch(1);
 80  *     CountDownLatch doneSignal = new CountDownLatch(N);
 81  *
 82  *     for (int i = 0; i < N; ++i) // create and start threads
 83  *       new Thread(new Worker(startSignal, doneSignal)).start();
 84  *
 85  *     doSomethingElse();            // don't let run yet
 86  *     startSignal.countDown();      // let all threads proceed
 87  *     doSomethingElse();
 88  *     doneSignal.await();           // wait for all to finish
 89  *   }
 90  * }
 91  *
 92  * class Worker implements Runnable {
 93  *   private final CountDownLatch startSignal;
 94  *   private final CountDownLatch doneSignal;
 95  *   Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
 96  *      this.startSignal = startSignal;
 97  *      this.doneSignal = doneSignal;
 98  *   }
 99  *   public void run() {
100  *      try {
101  *        startSignal.await();
102  *        doWork();
103  *        doneSignal.countDown();
104  *      } catch (InterruptedException ex) {} // return;
105  *   }
106  *
107  *   void doWork() { ... }
108  * }
109  *
110  * 
111 * 112 *

Another typical usage would be to divide a problem into N parts, 113 * describe each part with a Runnable that executes that portion and 114 * counts down on the latch, and queue all the Runnables to an 115 * Executor. When all sub-parts are complete, the coordinating thread 116 * will be able to pass through await. (When threads must repeatedly 117 * count down in this way, instead use a {@link CyclicBarrier}.) 118 * 119 *

120  * class Driver2 { // ...
121  *   void main() throws InterruptedException {
122  *     CountDownLatch doneSignal = new CountDownLatch(N);
123  *     Executor e = ...
124  *
125  *     for (int i = 0; i < N; ++i) // create and start threads
126  *       e.execute(new WorkerRunnable(doneSignal, i));
127  *
128  *     doneSignal.await();           // wait for all to finish
129  *   }
130  * }
131  *
132  * class WorkerRunnable implements Runnable {
133  *   private final CountDownLatch doneSignal;
134  *   private final int i;
135  *   WorkerRunnable(CountDownLatch doneSignal, int i) {
136  *      this.doneSignal = doneSignal;
137  *      this.i = i;
138  *   }
139  *   public void run() {
140  *      try {
141  *        doWork(i);
142  *        doneSignal.countDown();
143  *      } catch (InterruptedException ex) {} // return;
144  *   }
145  *
146  *   void doWork() { ... }
147  * }
148  *
149  * 
150 * 151 *

Memory consistency effects: Until the count reaches 152 * zero, actions in a thread prior to calling 153 * {@code countDown()} 154 * happen-before 155 * actions following a successful return from a corresponding 156 * {@code await()} in another thread. 157 * 158 * @since 1.5 159 * @author Doug Lea 160 */ 161 public class CountDownLatch { 162 /** 163 * Synchronization control For CountDownLatch. 164 * Uses AQS state to represent count. 165 */ 166 private static final class Sync extends AbstractQueuedSynchronizer { 167 private static final long serialVersionUID = 4982264981922014374L; 168 169 Sync(int count) { 170 setState(count); 171 } 172 173 int getCount() { 174 return getState(); 175 } 176 177 protected int tryAcquireShared(int acquires) { 178 return (getState() == 0) ? 1 : -1; 179 } 180 181 protected boolean tryReleaseShared(int releases) { 182 // Decrement count; signal when transition to zero 183 for (;;) { 184 int c = getState(); 185 if (c == 0) 186 return false; 187 int nextc = c-1; 188 if (compareAndSetState(c, nextc)) 189 return nextc == 0; 190 } 191 } 192 } 193 194 private final Sync sync; 195 196 /** 197 * Constructs a {@code CountDownLatch} initialized with the given count. 198 * 199 * @param count the number of times {@link #countDown} must be invoked 200 * before threads can pass through {@link #await} 201 * @throws IllegalArgumentException if {@code count} is negative 202 */ 203 public CountDownLatch(int count) { 204 if (count < 0) throw new IllegalArgumentException("count < 0"); 205 this.sync = new Sync(count); 206 } 207 208 /** 209 * Causes the current thread to wait until the latch has counted down to 210 * zero, unless the thread is {@linkplain Thread#interrupt interrupted}. 211 * 212 *

If the current count is zero then this method returns immediately. 213 * 214 *

If the current count is greater than zero then the current 215 * thread becomes disabled for thread scheduling purposes and lies 216 * dormant until one of two things happen: 217 *

    218 *
  • The count reaches zero due to invocations of the 219 * {@link #countDown} method; or 220 *
  • Some other thread {@linkplain Thread#interrupt interrupts} 221 * the current thread. 222 *
223 * 224 *

If the current thread: 225 *

    226 *
  • has its interrupted status set on entry to this method; or 227 *
  • is {@linkplain Thread#interrupt interrupted} while waiting, 228 *
229 * then {@link InterruptedException} is thrown and the current thread's 230 * interrupted status is cleared. 231 * 232 * @throws InterruptedException if the current thread is interrupted 233 * while waiting 234 */ 235 public void await() throws InterruptedException { 236 sync.acquireSharedInterruptibly(1); 237 } 238 239 /** 240 * Causes the current thread to wait until the latch has counted down to 241 * zero, unless the thread is {@linkplain Thread#interrupt interrupted}, 242 * or the specified waiting time elapses. 243 * 244 *

If the current count is zero then this method returns immediately 245 * with the value {@code true}. 246 * 247 *

If the current count is greater than zero then the current 248 * thread becomes disabled for thread scheduling purposes and lies 249 * dormant until one of three things happen: 250 *

    251 *
  • The count reaches zero due to invocations of the 252 * {@link #countDown} method; or 253 *
  • Some other thread {@linkplain Thread#interrupt interrupts} 254 * the current thread; or 255 *
  • The specified waiting time elapses. 256 *
257 * 258 *

If the count reaches zero then the method returns with the 259 * value {@code true}. 260 * 261 *

If the current thread: 262 *

    263 *
  • has its interrupted status set on entry to this method; or 264 *
  • is {@linkplain Thread#interrupt interrupted} while waiting, 265 *
266 * then {@link InterruptedException} is thrown and the current thread's 267 * interrupted status is cleared. 268 * 269 *

If the specified waiting time elapses then the value {@code false} 270 * is returned. If the time is less than or equal to zero, the method 271 * will not wait at all. 272 * 273 * @param timeout the maximum time to wait 274 * @param unit the time unit of the {@code timeout} argument 275 * @return {@code true} if the count reached zero and {@code false} 276 * if the waiting time elapsed before the count reached zero 277 * @throws InterruptedException if the current thread is interrupted 278 * while waiting 279 */ 280 public boolean await(long timeout, TimeUnit unit) 281 throws InterruptedException { 282 return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); 283 } 284 285 /** 286 * Decrements the count of the latch, releasing all waiting threads if 287 * the count reaches zero. 288 * 289 *

If the current count is greater than zero then it is decremented. 290 * If the new count is zero then all waiting threads are re-enabled for 291 * thread scheduling purposes. 292 * 293 *

If the current count equals zero then nothing happens. 294 */ 295 public void countDown() { 296 sync.releaseShared(1); 297 } 298 299 /** 300 * Returns the current count. 301 * 302 *

This method is typically used for debugging and testing purposes. 303 * 304 * @return the current count 305 */ 306 public long getCount() { 307 return sync.getCount(); 308 } 309 310 /** 311 * Returns a string identifying this latch, as well as its state. 312 * The state, in brackets, includes the String {@code "Count ="} 313 * followed by the current count. 314 * 315 * @return a string identifying this latch, as well as its state 316 */ 317 public String toString() { 318 return super.toString() + "[Count = " + sync.getCount() + "]"; 319 } 320 }

复制代码

CountDownLatch是通过“共享锁”实现的。下面,我们分析CountDownLatch中3个核心函数: CountDownLatch(int count), await(), countDown()。

 

1. CountDownLatch(int count)

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

说明:该函数是创建一个Sync对象,而Sync是继承于AQS类。Sync构造函数如下:

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

 

setState()在AQS中实现,源码如下:

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

说明:在AQS中,state是一个private volatile long类型的对象。对于CountDownLatch而言,state表示的”锁计数器“。CountDownLatch中的getCount()最终是调用AQS中的getState(),返回的state对象,即”锁计数器“。

 

2. await()

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

说明:该函数实际上是调用的AQS的acquireSharedInterruptibly(1);

AQS中的acquireSharedInterruptibly()的源码如下:

复制代码
public final void acquireSharedInterruptibly(long arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
复制代码

说明:acquireSharedInterruptibly()的作用是获取共享锁。
如果当前线程是中断状态,则抛出异常InterruptedException。否则,调用tryAcquireShared(arg)尝试获取共享锁;尝试成功则返回,否则就调用doAcquireSharedInterruptibly()。doAcquireSharedInterruptibly()会使当前线程一直等待,直到当前线程获取到共享锁(或被中断)才返回。

tryAcquireShared()在CountDownLatch.java中被重写,它的源码如下:

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

说明:tryAcquireShared()的作用是尝试获取共享锁。
如果"锁计数器=0",即锁是可获取状态,则返回1;否则,锁是不可获取状态,则返回-1。

复制代码
private void doAcquireSharedInterruptibly(long arg)
    throws InterruptedException {
    // 创建"当前线程"的Node节点,且Node中记录的锁是"共享锁"类型;并将该节点添加到CLH队列末尾。
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            // 获取上一个节点。
            // 如果上一节点是CLH队列的表头,则"尝试获取共享锁"。
            final Node p = node.predecessor();
            if (p == head) {
                long r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // (上一节点不是CLH队列的表头) 当前线程一直等待,直到获取到共享锁。
            // 如果线程在等待过程中被中断过,则再次中断该线程(还原之前的中断状态)。
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

说明
(01) addWaiter(Node.SHARED)的作用是,创建”当前线程“的Node节点,且Node中记录的锁的类型是”共享锁“(Node.SHARED);并将该节点添加到CLH队列末尾。关于Node和CLH在"Java多线程系列--“JUC锁”03之 公平锁(一)"已经详细介绍过,这里就不再重复说明了。
(02) node.predecessor()的作用是,获取上一个节点。如果上一节点是CLH队列的表头,则”尝试获取共享锁“。
(03) shouldParkAfterFailedAcquire()的作用和它的名称一样,如果在尝试获取锁失败之后,线程应该等待,则返回true;否则,返回false。
(04) 当shouldParkAfterFailedAcquire()返回ture时,则调用parkAndCheckInterrupt(),当前线程会进入等待状态,直到获取到共享锁才继续运行。
doAcquireSharedInterruptibly()中的shouldParkAfterFailedAcquire(), parkAndCheckInterrupt等函数在"Java多线程系列--“JUC锁”03之 公平锁(一)"中介绍过,这里也就不再详细说明了。

 

3. countDown()

public void countDown() {
    sync.releaseShared(1);
}

说明:该函数实际上调用releaseShared(1)释放共享锁。

releaseShared()在AQS中实现,源码如下:

复制代码
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
复制代码

说明:releaseShared()的目的是让当前线程释放它所持有的共享锁。
它首先会通过tryReleaseShared()去尝试释放共享锁。尝试成功,则直接返回;尝试失败,则通过doReleaseShared()去释放共享锁。

tryReleaseShared()在CountDownLatch.java中被重写,源码如下:

复制代码
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        // 获取“锁计数器”的状态
        int c = getState();
        if (c == 0)
            return false;
        // “锁计数器”-1
        int nextc = c-1;
        // 通过CAS函数进行赋值。
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
复制代码

说明:tryReleaseShared()的作用是释放共享锁,将“锁计数器”的值-1。

 

总结:CountDownLatch是通过“共享锁”实现的。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count给线程同时获取。当某线程调用该CountDownLatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行!

以上,就是CountDownLatch的实现原理。

 

CountDownLatch的使用示例

下面通过CountDownLatch实现:"主线程"等待"5个子线程"全部都完成"指定的工作(休眠1000ms)"之后,再继续运行。

复制代码
 1 import java.util.concurrent.CountDownLatch;
 2 import java.util.concurrent.CyclicBarrier;
 3 
 4 public class CountDownLatchTest1 {
 5 
 6     private static int LATCH_SIZE = 5;
 7     private static CountDownLatch doneSignal;
 8     public static void main(String[] args) {
 9 
10         try {
11             doneSignal = new CountDownLatch(LATCH_SIZE);
12 
13             // 新建5个任务
14             for(int i=0; i)
15                 new InnerThread().start();
16 
17             System.out.println("main await begin.");
18             // "主线程"等待线程池中5个任务的完成
19             doneSignal.await();
20 
21             System.out.println("main await finished.");
22         } catch (InterruptedException e) {
23             e.printStackTrace();
24         }
25     }
26 
27     static class InnerThread extends Thread{
28         public void run() {
29             try {
30                 Thread.sleep(1000);
31                 System.out.println(Thread.currentThread().getName() + " sleep 1000ms.");
32                 // 将CountDownLatch的数值减1
33                 doneSignal.countDown();
34             } catch (InterruptedException e) {
35                 e.printStackTrace();
36             }
37         }
38     }
39 }
复制代码

运行结果

复制代码
main await begin.
Thread-0 sleep 1000ms.
Thread-2 sleep 1000ms.
Thread-1 sleep 1000ms.
Thread-4 sleep 1000ms.
Thread-3 sleep 1000ms.
main await finished.
复制代码

结果说明:主线程通过doneSignal.await()等待其它线程将doneSignal递减至0。其它的5个InnerThread线程,每一个都通过doneSignal.countDown()将doneSignal的值减1;当doneSignal为0时,main被唤醒后继续执行。

  

你可能感兴趣的:(Java多线程)