1. 程式人生 > >JAVA同步工具類——CountDownLatch

JAVA同步工具類——CountDownLatch

閉鎖

在學習CountDownLatch之前,讓我們先了解一下閉鎖的概念。
閉鎖是一種同步工具類,可以延遲執行緒的進度直到其到達終止狀態;閉鎖的作用相當於一扇門,在閉鎖到達結束狀態之前,這扇門一直是關閉的,並且沒有任何執行緒能通過,當到達結束狀態時,這扇門會開啟並允許所有執行緒通過;當閉鎖到達結束狀態後,將不會再改變狀態,因此這扇門將永遠保持開啟狀態;

閉鎖可以用來確保某些活動直到其它活動都完成後才繼續執行;適用場景:

  • 應用程式的主執行緒希望在負責啟動框架服務的執行緒已經啟動所有的框架服務之後再執行;
  • 多玩家遊戲中當所有玩家都就緒後才執行某項活動;
  • 設想有這樣一個功能需要Thread1、Thread2、Thread3、Thread4四條執行緒分別統計C、D、E、F四個盤的大小,所有執行緒都統計完畢交給主執行緒去做彙總,利用閉鎖來完成就非常輕鬆;

閉鎖狀態包括一個計數器,該計數器被初始化為一個整數,表示需要等待的事件數量;

CountDownLatch

CountDownLatch是閉鎖的一種實現;CountDownLatch是在java1.5被引入;
CountDownLatch這個類能夠使一個執行緒等待其他執行緒完成各自的工作後再執行;

public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

主要API:

  • countDown():該方法遞減計數器,表示有一個事件已經發生;
  • await():該方法等待計時器達到零,達到零後表示需要等待的所有事件都已發生;

如果計數器的值非零,await方法會一直阻塞直到計數器為零,或者等待中的執行緒中斷,或者等待超時;

使用場景之——起始門(Starting Gate)

所有子執行緒等待計數器為零後一起執行

public class Appliction {
private final static int NUM = 10;

public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < NUM; i++) {
new Thread(() -> {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(Thread.currentThread().getName() + " started:" + System.currentTimeMillis());
}).start();
}
countDownLatch.countDown();
System.err.println("main thread exec end");
}
}
使用場景之——結束門(Ending Gate)

等待所有子任務或子執行緒結束後(計數器為零),對執行結果進行統計或彙總

/**
* 假設有10塊磁碟,需要10個執行緒同時統計磁碟空間大小,統計完成後由主執行緒進行彙總
*/
public class Appliction {
private final static int NUM = 10;

public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(NUM);
List<Disk> tasks = new ArrayList<>(NUM);
for (int i = 0; i < NUM; i++) {
tasks.add(new Disk());
}
for (Disk dk : tasks) {
new Thread(new DiskCountTask(countDownLatch, dk)).start();
}
countDownLatch.await();
int size = tasks.stream().mapToInt(Disk::getSize).sum();
System.err.println("All disk space size:" + size);
}


}

class Disk {
private Integer size;

public Integer getSize() {
return size;
}

public void setSize(Integer size) {
this.size = size;
}
}

class DiskCountTask implements Runnable {
private Disk disk;
private CountDownLatch downLatch;

public DiskCountTask(CountDownLatch downLatch, Disk disk) {
this.downLatch = downLatch;
this.disk = disk;
}

@Override
public void run() {
int size = new Random().nextInt(10);
try {
TimeUnit.SECONDS.sleep(size);
} catch (InterruptedException e) {
e.printStackTrace();
}
disk.setSize(size);
System.err.println(Thread.currentThread().getName() + " exec end[" + System.currentTimeMillis() + "], size:" + size);
downLatch.countDown();
}
}