在JDK的併發包裏提供了不少有意思的併發工具類。CountDownLatch、CyclicBarrier和Semaphore 工具類提供了一種併發流程控制的手段,Exchanger 工具類則提供了在線程間交換數據的一種手段。算法
CountDownLatch容許一個或多個線程等待其餘線程完成操做。數據庫
其實最簡單的作噶是使用join()方法,join用於讓當前執行線程等待join線程執行結束。其實現原理是不停檢查join線程是否存活,若是join線程存活則讓當前線程永遠等待。其中,wait(0) 表示永遠等待下去,代碼片斷以下:編程
while (isAlive()) { wait(0); }
知道線程停止後,線程的 this.notifyAll() 方法被調用,調用 notifyAll() 方法是在 JVM裏實現的,因此在JDK裏看不到,你們能夠查看JVM源碼。多線程
在JDK1.5以後的併發包CountDownLatch也能夠實現join的功能,而且功能更多,更強大。併發
示例代碼:app
運行結果:函數
1 2 3
CountDownLatch的構造方法接收一個int類型的參數做爲計數器,若是你想等待N個點完成,這裏就傳入N。工具
當咱們調用CountDownLatch的countDown()方法時,N就會減1,CountDownLatch的 await() 方法會阻塞當前線程,直到N變成零。因爲countDown()方法能夠用在任何地方,因此這裏說的N個點,也能夠是N個線程。用在多個線程時,你只須要把這個CountDownLatch的引用傳遞到線程裏。ui
若是有某個線程處理的比較慢,咱們不可能讓主線程一直等待,因此咱們可使用另一個帶指定時間的await方法,await(long time, TimeUnit unit), 這個方法等待特定時間後,就會再也不阻塞當前線程。join也有相似的方法。this
注意:計數器必須大於等於0,只是等於0時候,計數器就是零,調用await方法時不會阻塞當前線程。CountDownLatch不可能從新初始化或者修改CountDownLatch對象的內部計數器的值。一個線程調用countDown方法 happen-before 另一個線程調用await方法。
CyclicBarrier 的字面意思是可循環使用(Cyclic)的屏障(Barrier)。它要作的事情是,讓一組線程到達一個屏障(也能夠叫同步點)時被阻塞,直到最後一個線程到達屏障時,屏障纔會開門,全部被屏障攔截的線程纔會繼續運行。
2.1 構造方法
CyclicBarrier默認的構造方法是CyclicBarrier(int parties),其參數表示屏障攔截的線程數量,每一個線程調用await方法告訴CyclicBarrier我已經到達了屏障,而後當前線程被阻塞。
示例代碼:
運行結果:
1 2
或者
2 1
若是把new CyclicBarrier(2)修改爲new CyclicBarrier(3)則主線程和子線程會永遠等待,由於沒有第三個線程執行await方法,即沒有第三個線程到達屏障,因此以前到達屏障的兩個線程都不會繼續執行
CyclicBarrier還提供一個更高級的構造函數CyclicBarrier(int parties, Runnable barrierAction),用於在線程到達屏障時,優先執行barrierAction,方便處理更復雜的業務場景。
示例代碼:
運行結果:
3 1 2
2.2 應用場景
CyclicBarrier能夠用於多線程計算數據,最後合併計算結果的應用場景。好比咱們用一個Excel保存了用戶全部銀行流水,每一個Sheet保存一個賬戶近一年的每筆銀行流水,如今須要統計用戶的日均銀行流水,先用多線程處理每一個sheet裏的銀行流水,都執行完以後,獲得每一個sheet的日均銀行流水,最後,再用barrierAction用這些線程的計算結果,計算出整個Excel的日均銀行流水。
CountDownLatch的計數器只能使用一次。而CyclicBarrier的計數器可使用reset() 方法重置。因此CyclicBarrier能處理更爲複雜的業務場景,例如,若是計算髮生錯誤,能夠重置計數器,並讓線程們從新執行一次。
CyclicBarrier還提供其餘有用的方法,好比getNumberWaiting方法能夠得到CyclicBarrier阻塞的線程數量。isBroken方法用來知道阻塞的線程是否被中斷。
示例代碼:
運行結果:
true
3.1 做用
Semaphore(信號量)是用來控制同時訪問特定資源的線程數量,它經過協調各個線程,以保證合理的使用公共資源。
3.2 簡介
Semaphore也是一個線程同步的輔助類,能夠維護當前訪問自身的線程個數,並提供了同步機制。使用Semaphore能夠控制同時訪問資源的線程個數,例如,實現一個文件容許的併發訪問數。
3.3 應用場景
Semaphore能夠用於作流量控制,特別公用資源有限的應用場景,好比數據庫鏈接。假若有一個需求,要讀取幾萬個文件的數據,由於都是IO密集型任務,咱們能夠啓動幾十個線程併發的讀取,可是若是讀到內存後,還須要存儲到數據庫中,而數據庫的鏈接數只有10個,這時咱們必須控制只有十個線程同時獲取數據庫鏈接保存數據,不然會報錯沒法獲取數據庫鏈接。這個時候,咱們就可使用Semaphore來作流控,代碼以下:
在代碼中,雖然有30個線程在執行,可是隻容許10個併發的執行。Semaphore的構造方法Semaphore(int permits) 接受一個整型的數字,表示可用的許可證數量。Semaphore(10)表示容許10個線程獲取許可證,也就是最大併發數是10。Semaphore的用法也很簡單,首先線程使用Semaphore的acquire()獲取一個許可證,使用完以後調用release()歸還許可證。還能夠用tryAcquire()方法嘗試獲取許可證。
3.4 其餘方法
Semaphore還提供一些其餘方法:
Exchanger(交換者)是一個用於線程間協做的工具類。Exchanger用於進行線程間的數據交換。它提供一個同步點,在這個同步點兩個線程能夠交換彼此的數據。這兩個線程經過exchange方法交換數據, 若是第一個線程先執行exchange方法,它會一直等待第二個線程也執行exchange,當兩個線程都到達同步點時,這兩個線程就能夠交換數據,將本線程生產出來的數據傳遞給對方。
應用場景
一、Exchanger能夠用於遺傳算法,遺傳算法裏須要選出兩我的做爲交配對象,這時候會交換兩人的數據,並使用交叉規則得出2個交配結果。
二、Exchanger也能夠用於校對工做。好比咱們須要將紙製銀流經過人工的方式錄入成電子銀行流水,爲了不錯誤,採用AB崗兩人進行錄入,錄入到Excel以後,系統須要加載這兩個Excel,並對這兩個Excel數據進行校對,看看是否錄入的一致。代碼以下:
運行結果:
在B中獲取到錄入的A是:銀行流水A
若是兩個線程有一個沒有到達exchange方法,則會一直等待,若是擔憂有特殊狀況發生,避免一直等待,可使用exchange(V data, long time, TimeUnit unit)設置最大等待時長。
參考
《Java併發編程的藝術》方、魏、程著