這是在兩個或多個線程之間實現同步的非常方便的類,在該類中,一個或多個線程可以等待,直到在其他線程中執行的一組操作完成為止(請參閱javadoc和此文章 )。 CountDownLatch在適當的情況下可以節省您的時間,您必須了解此類。
與往常一樣,如果代碼不好,線程同步會引發死鎖。 由于并發用例可能非常復雜,因此開發人員必須非常小心。 在這里我不會描述復雜的并發問題,但是如果您不小心使用CountDownLatch ,可能會遇到一個簡單的問題。
假設您有2個線程(線程1和線程2)共享一個java.util.concurrent.ArrayBlockingQueue,并且您想使用CountDownLatch對其進行同步。 檢查以下簡單示例:
package gr.qiozas.simple.threads.countdownlatch;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;public class DeadlockCaseCDL {public static void main(String[] args) throws InterruptedException {CountDownLatch c = new CountDownLatch(1);ArrayBlockingQueue b = new ArrayBlockingQueue(1);new Thread(new T1(c, b)).start();new Thread(new T2(c, b)).start();}private static class T1 implements Runnable {private CountDownLatch c;private ArrayBlockingQueue b;private T1(CountDownLatch c, ArrayBlockingQueue b) {this.c = c; this.b = b;}public void run() {try {b.put(234);b.put(654);doWork(T1.class);c.countDown();doWork(T1.class);} catch (InterruptedException ex) {}}}private static class T2 implements Runnable {private CountDownLatch c;private ArrayBlockingQueue b;private T2(CountDownLatch c, ArrayBlockingQueue b) {this.c = c; this.b = b;}public void run() {try {doWork(T2.class);c.await();doWork(T2.class);System.out.println("I just dequeue "+b.take());System.out.println("I just dequeue "+b.take());} catch (InterruptedException ex) {}}}private static void doWork(Class classz) {System.out.println(classz.getName()+" do the work");}
}
您在上面看到的是,主線程創建了一個計數為1的CountDownLatch。 和一個容量為“ 1”的ArrayBlockingQueue 然后生成“ 2個線程”。 ArrayBlockingQueue將用于實際的“工作”(入隊和出隊),而CountDownLatch將用于同步線程(入隊必須在出隊前完成)。
線程1使2條消息入隊,線程2希望使它們出隊,但僅在線程1使兩條消息入隊之后。 此同步由CountDownLatch保證。 您認為此代碼可以嗎? 不,這不是造成死鎖的原因!
如果仔細看第10行,您將看到我初始化ArrayBlockingQueue的容量等于“ 1”。 但是線程1要排隊2條消息,然后釋放(CountDownLatch)的鎖,以便隨后被線程2占用。 容量“ 1? 隊列阻塞線程1,直到另一個線程將一個消息從隊列中出隊,然后再次嘗試使第二條消息入隊。 不幸的是,只有線程2使消息從隊列中出隊,但是由于線程1擁有CountDownLatch鎖,因此線程2無法使任何消息出隊,因此被阻塞。 因此,由于兩個線程都被阻塞(等待獲取不同的鎖),我們確實有一個死鎖 。 線程1等待ArrayBlockingQueue鎖定,而線程2等待CountDownLatch鎖定(您也可以在下面的相關線程轉儲中看到它)。
如果我們增加隊列的容量,那么此代碼將毫無問題地運行,但這不是本文的重點。 您需要了解的是,必須謹慎使用CountDownLatch,以避免此類危險情況。 您必須了解類的功能情況,向團隊的其他開發人員詳細說明該功能,編寫有用的Javadoc,并始終編寫在極端情況下(不僅對于開心路徑而言)都可靠的代碼。
您可能會幫助您的另一點是,現代JVM無法檢測到此死鎖。 嘗試一下。
如您所知,現代JVM(Hotspot和JRockit)都能夠檢測到簡單的死鎖,并在線程轉儲中報告它們。 請參閱從Hotspot JVM檢測到的簡單死鎖示例:
Found one Java-level deadlock:
=============================
"Thread-6":
waiting to lock monitor 0x00a891ec (object 0x06c616e0, a java.lang.String),
which is held by "Thread-9"
"Thread-9":
waiting to lock monitor 0x00a8950c (object 0x06c61708, a java.lang.String),
which is held by "Thread-6"
您可以嘗試DeadlockCaseCDL并獲得線程轉儲(在GNU / Linux上僅運行“ kill -3 ?jvm_pid?”)。 您將看到線程轉儲看起來很正常,JVM沒有檢測到死鎖,但是您處于死鎖狀態!!! 因此,請注意,JVM無法檢測到這種死鎖。
從我的本地執行中檢查以下線程轉儲示例:
Full thread dump Java HotSpot(TM) Server VM (17.1-b03 mixed mode):"DestroyJavaVM" prio=10 tid=0x0946e800 nid=0x5382 waiting on condition [0x00000000]java.lang.Thread.State: RUNNABLE"Thread-1" prio=10 tid=0x094b1400 nid=0x5393 waiting on condition [0x7c79a000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for (a java.util.concurrent.CountDownLatch$Sync)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158)at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:969)at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1281)at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:207)at gr.qiozas.simple.threads.countdownlatch.DeadlockCaseCDL$T2.run(DeadlockCaseCDL.java:50)at java.lang.Thread.run(Thread.java:662)"Thread-0" prio=10 tid=0x094afc00 nid=0x5392 waiting on condition [0x7c7eb000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158)at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987)at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:252)at gr.qiozas.simple.threads.countdownlatch.DeadlockCaseCDL$T1.run(DeadlockCaseCDL.java:29)at java.lang.Thread.run(Thread.java:662)"Low Memory Detector" daemon prio=10 tid=0x0947f800 nid=0x5390 runnable [0x00000000]java.lang.Thread.State: RUNNABLE"CompilerThread1" daemon prio=10 tid=0x7cfa9000 nid=0x538f waiting on condition [0x00000000]java.lang.Thread.State: RUNNABLE"CompilerThread0" daemon prio=10 tid=0x7cfa7000 nid=0x538e waiting on condition [0x00000000]java.lang.Thread.State: RUNNABLE"Signal Dispatcher" daemon prio=10 tid=0x7cfa5800 nid=0x538d waiting on condition [0x00000000]java.lang.Thread.State: RUNNABLE"Finalizer" daemon prio=10 tid=0x7cf96000 nid=0x538c in Object.wait() [0x7cd15000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)- locked (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134)at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)"Reference Handler" daemon prio=10 tid=0x7cf94800 nid=0x538b in Object.wait() [0x7cd66000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on (a java.lang.ref.Reference$Lock)at java.lang.Object.wait(Object.java:485)at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)- locked (a java.lang.ref.Reference$Lock)"VM Thread" prio=10 tid=0x7cf92000 nid=0x538a runnable"GC task thread#0 (ParallelGC)" prio=10 tid=0x09475c00 nid=0x5383 runnable"GC task thread#1 (ParallelGC)" prio=10 tid=0x09477000 nid=0x5384 runnable"GC task thread#2 (ParallelGC)" prio=10 tid=0x09478800 nid=0x5385 runnable"GC task thread#3 (ParallelGC)" prio=10 tid=0x0947a000 nid=0x5387 runnable"VM Periodic Task Thread" prio=10 tid=0x09489800 nid=0x5391 waiting on conditionJNI global references: 854HeapPSYoungGen total 14976K, used 1029K [0xa2dd0000, 0xa3e80000, 0xb39d0000)eden space 12864K, 8% used [0xa2dd0000,0xa2ed1530,0xa3a60000)from space 2112K, 0% used [0xa3c70000,0xa3c70000,0xa3e80000)to space 2112K, 0% used [0xa3a60000,0xa3a60000,0xa3c70000)PSOldGen total 34304K, used 0K [0x815d0000, 0x83750000, 0xa2dd0000)object space 34304K, 0% used [0x815d0000,0x815d0000,0x83750000)PSPermGen total 16384K, used 1739K [0x7d5d0000, 0x7e5d0000, 0x815d0000)object space 16384K, 10% used [0x7d5d0000,0x7d782e90,0x7e5d0000)
參考: 有益的CountDownLatch和我們的JCG合作伙伴 Adrianos Dadis 遇到的棘手的Java僵局 ,位于“ Java,集成和源代碼的美德”博客上 。
- Java并發教程– CountDownLatch
- 并發優化–減少鎖粒度
- Java內存模型–快速概述和注意事項
- Java并發教程–線程池
- Java并發教程–信號量
- Java教程和Android教程列表
翻譯自: https://www.javacodegeeks.com/2011/11/beneficial-countdownlatch-and-tricky.html