個人博客請訪問 http://www.x0100.top
CountDownLatch是JDK提供的一個同步工具,它可以讓一個或多個線程等待,一直等到其他線程中執行完成一組操作。CountDown方法和Await方法,CountDownLatch在初始化時,需要指定用給定一個整數作爲計數器。當調用CountDown方法時,計數器會被減1;當調用Await方法時,如果計數器大於0時,線程會被阻塞,一直到計數器被CountDown方法減到0時,線程纔會繼續執行。計數器是無法重置的,當計數器被減到0時,調用Await方法都會直接返回。
使用CountDownLatch模擬如下場景:
張三、李四和王五幾個人約好去飯店一起去吃飯,這幾個人都是比較紳士,要等到所有人都到齊以後才讓服務員上菜。這種場景就可以用到CountDownLatch。
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class Customer implements Runnable {
private CountDownLatch latch;
private String name;
public Customer(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}
@Override
public void run() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
Random random = new Random();
System.out.println(sdf.format(new Date()) + " " + name + "出發去飯店");
Thread.sleep((long) (random.nextDouble() * 3000) + 1000);
System.out.println(sdf.format(new Date()) + " " + name + "到了飯店");
latch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class Waitress implements Runnable {
private CountDownLatch latch;
private String name;
public Waitress(CountDownLatch latch, String name) {
this.latch = latch;
this.name = name;
}
@Override
public void run() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
System.out.println(sdf.format(new Date()) + " " + name + "等待顧客");
latch.await();
System.out.println(sdf.format(new Date()) + " " + name + "開始上菜");
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTester {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
List<Thread> threads = new ArrayList<>(3);
threads.add(new Thread(new Customer(latch, "張三")));
threads.add(new Thread(new Customer(latch, "李四")));
threads.add(new Thread(new Customer(latch, "王五")));
for (Thread thread : threads) {
thread.start();
}
Thread.sleep(100);
new Thread(new Waitress(latch, "♥小芳♥")).start();
}
}
運行結果
15:25:53.015 王五出發去飯店
15:25:53.015 李四出發去飯店
15:25:53.015 張三出發去飯店
15:25:53.062 ♥小芳♥等待顧客
15:25:54.341 張三到了飯店
15:25:54.358 李四到了飯店
15:25:56.784 王五到了飯店
15:25:56.784 ♥小芳♥開始上菜
Await方法的另一個重載,傳入等待的超時時間,比如服務員只等3秒鐘,可以把服務員類中的:
latch.await();
改成:
latch.await(3, TimeUnit.SECONDS);
CountDownLatch的實現原理
CountDownLatch有一個內部類叫做Sync,它繼承了AbstractQueuedSynchronizer類,其中維護了一個整數state,並且保證了修改state的可見性和原子性。
創建CountDownLatch實例時,也會創建一個Sync的實例,同時把計數器的值傳給Sync實例,具體是這樣的:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
在CountDown方法中,只調用了Sync實例的ReleaseShared方法,具體是這樣的:
public void countDown() {
sync.releaseShared(1);
}
其中的ReleaseShared方法,先對計數器進行減1操作,如果減1後的計數器爲0,喚醒被Await方法阻塞的所有線程,具體是這樣的:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) { //對計數器進行減一操作
doReleaseShared();//如果計數器爲0,喚醒被await方法阻塞的所有線程
return true;
}
return false;
}
其中的TryReleaseShared方法,先獲取當前計數器的值,如果計數器爲0時,就直接返回;如果不爲0時,使用CAS方法對計數器進行減1操作,具體是這樣的:
protected boolean tryReleaseShared(int releases) {
for (;;) {//死循環,如果CAS操作失敗就會不斷繼續嘗試。
int c = getState();//獲取當前計數器的值。
if (c == 0)// 計數器爲0時,就直接返回。
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))// 使用CAS方法對計數器進行減1操作
return nextc == 0;//如果操作成功,返回計數器是否爲0
}
}
在Await方法中,只調用了Sync實例的AcquireSharedInterruptibly方法,具體是這樣的:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
其中AcquireSharedInterruptibly方法,判斷計數器是否爲0,如果不爲0則阻塞當前線程,具體是這樣的:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//判斷計數器是否爲0
doAcquireSharedInterruptibly(arg);//如果不爲0則阻塞當前線程
}
其中TryAcquireShared方法,是AbstractQueuedSynchronizer中的一個模板方法,其具體實現在Sync類中,其主要是判斷計數器是否爲零,如果爲零則返回1,如果不爲零則返回-1,具體是這樣的:
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}