天天看点

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