線程-并發工具-CountDownLatch
文章目錄
- 線程-并發工具-CountDownLatch
- 一、CountDownLatch是什麼?
- 二、主要參數與方法
-
- 1.主要方法
- 2.構造方法
- 三、原理
-
- 1.核心方法源碼
- 四、實踐
-
- 1.用法一:一個線程等待其他多個線程都執行完畢,再繼續自己的工作
- 2.用法二:多個線程等待某一個線程的信号,同時開始執行
- 總結
-
- 注意點
一、CountDownLatch是什麼?
CountDownLatch是一種并發流程控制的同步工具。主要的作用是等待多個線程同時完成任務之後,再繼續完成主線程任務。簡單點可以了解為,幾個小夥伴一起到火鍋店聚餐,人到齊了,火鍋店才可以開飯。
二、主要參數與方法
1.主要方法
count 數量,了解為小夥伴的個數
//減少鎖存器的計數,如果計數達到零,則釋放所有等待線程。
//計數器
public void countDown() {
sync.releaseShared(1);
}
//導緻目前線程等待,直到鎖存器遞減至零為止,除非該線程被中斷。
//火鍋店調用await的線程,count為0才能繼續執行
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
2.構造方法
代碼如下:
//初始化值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
//擷取剩餘的數量
public long getCount() {
return sync.getCount();
}
三、原理
我們可以看出countDown()是CountDownLatch的核心方法,我來看下他的具體實作。

CountDownLatch來時繼承AQS的共享模式來完成其的實作,從前面的學習得出AQS主要是依賴同步隊列和state實作控制。
什麼是共享模式?
這裡與獨占鎖大多數相同,自旋過程中的退出條件是是目前節點的前驅節點是頭結點并且tryAcquireShared(arg)傳回值大于等于0即能成功獲得同步狀态.
1.核心方法源碼
await
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//當任務數量不為0挂起,線程排隊
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//在共享模式下擷取
doAcquireSharedInterruptibly(int arg)
countDown
public void countDown() {
sync.releaseShared(1);
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
//自旋防止失敗
for (;;) {
//擷取剩餘的任務數量
int c = getState();
//任務為0傳回false
if (c == 0)
return false;
//調用cas來進行替換,也保證了線程安全,當為0的時候喚醒
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
//當任務數量為0,aqs的釋放共享鎖
void doReleaseShared()
四、實踐
1.用法一:一個線程等待其他多個線程都執行完畢,再繼續自己的工作
public class CountDownLatchTest {
private static Lock lock = new ReentrantLock();
private static CountDownLatch countDownLatch = new CountDownLatch(4);
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
IntStream.range(0,4).forEach(i ->{
executorService.submit(()->{
lock.lock();
System.out.println(Thread.currentThread().getName()+ "來火鍋店吃火鍋!");
try {
Thread.sleep(1000);
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName() + "我到火鍋店了,準備開吃!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
});
try {
countDownLatch.await();
System.out.println("人到齊了,開飯");
executorService.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
輸出結果
代碼中設定了一個CountDownLatch做倒計時,四個人(count為4)一起到火鍋店吃飯,每到一個人計數器就減去1(countDownLatch.countDown()),當計數器為0的時候,main線程在await的阻塞結束,繼續往下執行。
2.用法二:多個線程等待某一個線程的信号,同時開始執行
用搶位子作為例子,将線程挂起等待,同時開始執行。
public class CountDownLatchTest2 {
private static Lock lock = new ReentrantLock();
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
IntStream.range(0,4).forEach(i ->{
executorService.submit(()->{
System.out.println(Thread.currentThread().getName()+ "準備開始搶位子!");
try {
//Thread.sleep(1000);
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "搶到了位置");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
});
try {
Thread.sleep(5000);
System.out.println("五秒後開始搶位置");
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.shutdown();
}
}
總結
我們可以看到CountDownLatch的使用很簡單,就當做一個計時器來使用,在控制并發方面能給我們提供幫助。
- 在構造器中初始化任務數量
- 調用await()挂起主線程
- 調用countDown()方法,減一,直到為0 的時候繼續執行await。
注意點
- CountDownLatch是不能重用的。