java并發(fā)學(xué)習(xí)-CountDownLatch實(shí)現(xiàn)原理全面講解
CountDownLatch在多線程并發(fā)編程中充當(dāng)一個(gè)計(jì)時(shí)器的功能,并且維護(hù)一個(gè)count的變量,并且其操作都是原子操作。
如下圖,內(nèi)部有下static final的Sync類繼承自AQS.

該類主要通過(guò)countDown()和await()兩個(gè)方法實(shí)現(xiàn)功能的,首先通過(guò)建立CountDownLatch對(duì)象,并且傳入?yún)?shù)即為count初始值。
如果一個(gè)線程調(diào)用了await()方法,那么這個(gè)線程便進(jìn)入阻塞狀態(tài),并進(jìn)入阻塞隊(duì)列。
如果一個(gè)線程調(diào)用了countDown()方法,則會(huì)使count-1;當(dāng)count的值為0時(shí),這時(shí)候阻塞隊(duì)列中調(diào)用await()方法的線程便會(huì)逐個(gè)被喚醒,從而進(jìn)入后續(xù)的操作。

補(bǔ)充:Java并發(fā)包中CountDownLatch的工作原理、使用示例
1. CountDownLatch的介紹
CountDownLatch是一個(gè)同步工具,它主要用線程執(zhí)行之間的協(xié)作。CountDownLatch 的作用和 Thread.join() 方法類似,讓一些線程阻塞直到另一些線程完成一系列操作后才被喚醒。在直接創(chuàng)建線程的年代(Java 5.0 之前),我們可以使用 Thread.join()。在線程池出現(xiàn)后,因?yàn)榫€程池中的線程不能直接被引用,所以就必須使用 CountDownLatch 了。
CountDownLatch主要有兩個(gè)方法,當(dāng)一個(gè)或多個(gè)線程調(diào)用await方法時(shí),這些線程會(huì)阻塞。其它線程調(diào)用countDown方法會(huì)將計(jì)數(shù)器減1(調(diào)用countDown方法的線程不會(huì)阻塞),當(dāng)計(jì)數(shù)器的值變?yōu)?時(shí),因await方法阻塞的線程會(huì)被喚醒,繼續(xù)執(zhí)行。
實(shí)現(xiàn)原理:計(jì)數(shù)器的值由構(gòu)造函數(shù)傳入,并用它初始化AQS的state值。當(dāng)線程調(diào)用await方法時(shí)會(huì)檢查state的值是否為0,如果是就直接返回(即不會(huì)阻塞);如果不是,將表示該節(jié)點(diǎn)的線程入列,然后將自身阻塞。當(dāng)其它線程調(diào)用countDown方法會(huì)將計(jì)數(shù)器減1,然后判斷計(jì)數(shù)器的值是否為0,當(dāng)它為0時(shí),會(huì)喚醒隊(duì)列中的第一個(gè)節(jié)點(diǎn),由于CountDownLatch使用了AQS的共享模式,所以第一個(gè)節(jié)點(diǎn)被喚醒后又會(huì)喚醒第二個(gè)節(jié)點(diǎn),以此類推,使得所有因await方法阻塞的線程都能被喚醒而繼續(xù)執(zhí)行。
從源代碼和實(shí)現(xiàn)原理中可以看出一個(gè)CountDownLatch對(duì)象,只能使用一次,不能重復(fù)使用。
await方法源碼
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
doAcquireSharedInterruptibly 主要實(shí)現(xiàn)線程的入列與阻塞。
countDown方法
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
doReleaseShared主要實(shí)現(xiàn)喚醒第一個(gè)節(jié)點(diǎn),第一個(gè)節(jié)點(diǎn)有會(huì)喚醒第二個(gè)節(jié)點(diǎn),……。
2. 使用示例
package demo;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo {
private CountDownLatch cdl = new CountDownLatch(2);
private Random rnd = new Random();
class FirstTask implements Runnable{
private String id;
public FirstTask(String id){
this.id = id;
}
@Override
public void run(){
System.out.println("Thread "+ id + " is start");
try {
Thread.sleep(rnd.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread "+ id + " is over");
cdl.countDown();
}
}
class SecondTask implements Runnable{
private String id;
public SecondTask(String id){
this.id = id;
}
@Override
public void run(){
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----------Thread "+ id + " is start");
try {
Thread.sleep(rnd.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----------Thread "+ id + " is over");
}
}
public static void main(String[] args){
ExecutorService es = Executors.newCachedThreadPool();
CountDownLatchDemo cdld = new CountDownLatchDemo();
es.submit(cdld.new SecondTask("c"));
es.submit(cdld.new SecondTask("d"));
es.submit(cdld.new FirstTask("a"));
es.submit(cdld.new FirstTask("b"));
es.shutdown();
}
}
在這個(gè)示例中,我們創(chuàng)建了四個(gè)線程a、b、c、d,這四個(gè)線程幾乎同時(shí)提交給了線程池。c線程和d線程會(huì)在a線程和b線程結(jié)束后開(kāi)始執(zhí)行。
運(yùn)行結(jié)果
Thread a is start Thread b is start Thread b is over Thread a is over ----------Thread c is start ----------Thread d is start ----------Thread d is over ----------Thread c is over
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Maven中exec插件執(zhí)行Java程序的實(shí)現(xiàn)
在Maven項(xiàng)目中,可以使用Maven的插件來(lái)執(zhí)行Java程序,本文主要介紹了Maven中exec插件執(zhí)行Java程序的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
Java實(shí)現(xiàn)的爬蟲(chóng)抓取圖片并保存操作示例
這篇文章主要介紹了Java實(shí)現(xiàn)的爬蟲(chóng)抓取圖片并保存操作,涉及Java針對(duì)頁(yè)面URL訪問(wèn)、獲取、字符串匹配、文件下載等相關(guān)操作技巧,需要的朋友可以參考下2018-08-08
Mybatis?Plus插入數(shù)據(jù)后獲取新數(shù)據(jù)id值的踩坑記錄
在某些情況下,需要在執(zhí)行新增后,需要獲取到新增行的id,這篇文章主要給大家介紹了關(guān)于Mybatis?Plus插入數(shù)據(jù)后獲取新數(shù)據(jù)id值的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08
最新IDEA?2022基于JVM極致優(yōu)化?IDEA啟動(dòng)速度的方法
這篇文章主要介紹了IDEA?2022最新版?基于?JVM極致優(yōu)化?IDEA?啟動(dòng)速度,需要的朋友可以參考下2022-08-08
Java 隊(duì)列 Queue 用法實(shí)例詳解
本文實(shí)例講述了Java內(nèi)置隊(duì)列類Queue用法,分享給大家供大家參考2017-04-04

