天天看點

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();
}
}