CountDownLatch
我之前遇到過這麼一個需求:“客戶端同時下載視頻、音頻和大量試題壓縮包”。我讓線程池分配三個線程同時開啓下載三類數據,等到它們都下載完成時再進行數據整合操作。問題來了,如何在沒有線程安全問題情況下監聽到這三個線程已經都執行完畢了呢?此時CountDownLatch類就是最佳選擇。
CountDownLatch是一個同步的輔助工具類,允許一個或多個線程,等待其它一組線程完成操作,再繼續執行。它其實就是一個倒計時同步鎖。
class DownloadVideo implements Runnable{
private CountDownLatch latch;
public DownloadVideo(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
try {
//啓動下載視頻......
latch.countDown();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class DownloadAudio implements Runnable{
private CountDownLatch latch;
public DownloadVideo(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
try {
//下載音頻......
latch.countDown();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class DownloadTestZip implements Runnable{
private CountDownLatch latch;
public DownloadVideo(CountDownLatch latch){
this.latch = latch;
}
@Override
public void run() {
try {
//下載音頻......
latch.countDown();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class IntegrationData implements Runnable{
@Override
public void run() {
//創建CountDownLatch計數器,計數次數:3
CountDownLatch latch = new CountDownLatch(3);
Executor executor = Executors.newFixedThreadPool(3);
executor.execute(new DownloadVideo(latch));
executor.execute(new DownloadAudio(latch));
executor.execute(new DownloadTestZip(latch));
// 3個線程countDown()都執行之後纔會釋放當前線程,程序才能繼續往後執行
latch.await();
//執行數據整合
}
}
CountDownLatch怎麼理解呢?
CountDownLatch是在線程安全的條件下計數,沒執行一次countDown方法計數器就會遞減1,直到計數器爲0,await方法以後的代碼纔會被執行。
使用情景舉例:
玩LOL時,平臺要求同組每位英雄都加載完成後,所有英雄纔可以進入遊戲環節。
CyclicBarrier
還是上面的項目實例,後來因爲CountDownLatch我發現了CyclicBarrier這個類,對於這個項目實例CyclicBarrier也可以實現。
class DownloadVideo implements Runnable{
private CyclicBarrier cb;
public DownloadVideo(CyclicBarrier cb){
this.cb = cb;
}
@Override
public void run() {
try {
//啓動下載視頻......
cb.await();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class DownloadAudio implements Runnable{
private CyclicBarrier cb;
public DownloadAudio(CyclicBarrier cb){
this.cb = cb;
}
@Override
public void run() {
try {
//下載音頻......
cb.await();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class DownloadTestZip implements Runnable{
private CyclicBarrier cb;
public DownloadTestZip(CyclicBarrier cb){
this.cb = cb;
}
@Override
public void run() {
try {
//下載音頻......
cb.await();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
class IntegrationData implements Runnable{
@Override
public void run() {
//整合數據
}
}
CyclicBarrier cb = new CyclicBarrier(3,new IntegrationData());
//開啓線程執行下載視頻、下載音頻和下載試題壓縮包任務
Executor executor = Executors.newFixedThreadPool(3);
executor.execute(new DownloadVideo(cb));
executor.execute(new DownloadAudio(cb));
executor.execute(new DownloadTestZip(cb));
當下載視頻、下載音頻和下載試題壓縮包線程都執行到await方法時,此時數據整合線程纔會執行。
CountDownLatch與CyclicBarrier的區別
CountDownLatch:線程安全,內部維護一計數器,每執行一次downCount方法,計數器減一,await方法會阻塞線程執行,直到計數器爲0時,纔會繼續await方法以後的代碼。
CyclicBarrier:線程安全,內部維護一計數器,每執行一次await方法,計數器減一,await方法會阻塞當前所在線程,直到計數器爲0時,會喚醒同一個Condition上所有線程,繼續執行。new CyclicBarrier(3,new IntegrationData()),當使用兩個參數的構造時,需要傳一個線程,此時,計數器被減至0時,會執行這個線程,當線程執行完畢之後,纔會喚醒同一個Condition上所有線程繼續執行。
//CyclicBarrier的dowait方法,await方法中調用了dowait方法。關鍵代碼就在這個方法中。
private int dowait(boolean timed, long nanos)
//線程同步鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
//計數器減一
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
//如果有兩個參數的構造傳了線程,那麼此時就執行這個線程。
if (command != null)
command.run();
ranAction = true;
//生命代切換
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
......
//以上執行完畢
for (;;) {
try {
//喚醒Condition上所有線程
if (!timed)
trip.await();
//喚醒超時時間
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
Thread.currentThread().interrupt();
}
}
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}