Java線程(十一):CountDownLatch-線程並發的發令槍 – JAVA編程語言程序開發技術文章

田徑賽百米短跑時,運動員會在起跑線做準備動作,等到發令槍一聲響,運動員就會奮力奔跑。在多線程運行時,也有這麼一個發令槍–CountDownLatch,它通過控制事先定義的計數來控制線程的運行。
       CountDownLatch的構造方法如下:
[java] view plaincopy
CountDownLatch(int count); //構造一個用給定計數初始化的 CountDownLatch。 

       主要用到的方法有:
[java] view plaincopy
void await(); //使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷。 
boolean await(long timeout, TimeUnit unit); //使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷或超出瞭指定的等待時間。 
void countDown(); //遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程。 
long getCount();  // 返回當前計數。 
String toString(); //返回標識此鎖存器及其狀態的字符串。 

       CountDownLatch是一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。對於給定的計數 初始化 CountDownLatch,可以調用瞭 countDown() 方法,在當前計數到達零之前,await() 方法會一直受阻塞,當計數到達零時,會釋放所有等待的線程,await() 的所有後續調用都將立即返回。但這種現象隻出現一次——計數無法被重置,如果需要重置計數,請考慮使用 CyclicBarrier。
       CountDownLatch 是一個通用同步工具,它有很多用途,將計數  初始化為1的 CountDownLatch可 用作一個簡單的開/關鎖存器或入口,在通過調用 countDown() 的線程打開入口前,所有調用 await() 的線程都一直在入口處等待。用 N 初始化的 CountDownLatch 可以使一個線程在 N 個線程完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。
       CountDownLatch 的一個有用特性是,它不要求調用 countDown() 方法的線程等到計數到達零時才繼續,而在所有線程都能通過之前,它隻是阻止任何線程繼續通過await(),它比CyclicBarrier有更大的靈活性,它可以控制不確定數目的線程,而不是像CyclicBarrier在確定數目的線程wait()時就會通過,隻有當countDown()的值為0時才允許所有的線程通過。
       下面通過一個例子加以說明:
[java]
public class CountdownLatchTest { 
    public static void main(String[] args) { 
        ExecutorService service = Executors.newCachedThreadPool(); 
        final CountDownLatch cdOrder = new CountDownLatch(1); 
        final CountDownLatch cdAnswer = new CountDownLatch(3);       
        for(int i=0;i<3;i++){ 
            Runnable runnable = new Runnable(){ 
                    public void run(){ 
                    try { 
                        System.out.println("線程" + Thread.currentThread().getName() +  
                                "正準備接受命令");          
                        cdOrder.await(); 
                        System.out.println("線程" + Thread.currentThread().getName() +  
                        "已接受命令");                                
                        Thread.sleep((long)(Math.random()*10000));   
                        System.out.println("線程" + Thread.currentThread().getName() +  
                                "回應命令處理結果");                 
                        cdAnswer.countDown();                        
                    } catch (Exception e) { 
                        e.printStackTrace(); 
                    }                
                } 
            }; 
            service.execute(runnable); 
        }        
        try { 
            Thread.sleep((long)(Math.random()*10000)); 
         
            System.out.println("線程" + Thread.currentThread().getName() +  
                    "即將發佈命令");                       
            cdOrder.countDown(); 
            System.out.println("線程" + Thread.currentThread().getName() +  
            "已發送命令,正在等待結果");     
            cdAnswer.await(); 
            System.out.println("線程" + Thread.currentThread().getName() +  
            "已收到所有響應結果");    
        } catch (Exception e) { 
            e.printStackTrace(); 
        }                
        service.shutdown(); 
    } 

       上例中main方法表示主線程,並且通過for循環創建3個子線程,定義瞭兩個CountDownLatch對象:cdOrder和cdAnswer,cdOrder計數為1,用來控制3個子線程,cdAnswer計數為3,用來控制主線程。當程序開始運行時,主線程和子線程同時開始運行,由於主線程需要sleep一段時間,所以3個子線程運行,但是碰到cdOrder.await();必須等到主線程cdOrder.countDown();將計數變成0時才可以繼續往下運行,主線程運行到cdAnswer.await();時等待,隻有當三個子線程都cdAnswer.countDown();將計數變為0時主線程才可以往下運行。改程序運行結果如下:
[java] 
線程pool-1-thread-2正準備接受命令 
線程pool-1-thread-3正準備接受命令 
線程pool-1-thread-1正準備接受命令 
線程main即將發佈命令 
線程main已發送命令,正在等待結果 
線程pool-1-thread-3已接受命令 
線程pool-1-thread-2已接受命令 
線程pool-1-thread-1已接受命令 
線程pool-1-thread-1回應命令處理結果 
線程pool-1-thread-3回應命令處理結果 
線程pool-1-thread-2回應命令處理結果 
線程main已收到所有響應結果 

 

發佈留言