1.简介
CountDownLatch是Java1.5之后引入的Java并发工具类,放在java.util.concurrent包下 面 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html 官方API。
CountDownLatch能够使一个或多个线程等待其他线程完成各自的工作后再执行;CountDownLatch是JDK 5+里面闭锁的一个实现。
闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态。通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成。
与CountDownLatch第一次交互是主线程等待其它的线程,主线程必须在启动其它线程后立即调用await方法,这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。
其他的N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务,这种机制就是通过countDown()方法来完成的。每调用一次这个方法,在构造函数中初始化的count值就减1,所以当N个线程都调用了这个方法count的值等于0,然后主线程就能通过await方法,恢复自己的任务。
这里的主线程是相对的概念,需要根据CountDownLatch创建的场景分析。
2.主要方法
特有方法:
public CountDownLatch(int count); //指定计数的次数,只能被设置1次
public void countDown(); //调用此方法则计数减1
public void await() throws InterruptedException //调用此方法会一直阻塞当前线程,直到计时器的值为0,除非线程被中断。
Public Long getCount(); //得到当前的计数
Public boolean await(long timeout, TimeUnit unit) //调用此方法会一直阻塞当前线程,直到计时器的值为0,除非线程被中断或者计数器超时,返回false代表计数器超时。
From Object Inherited:
Clone、equals、hashCode、notify、notifyALL、wait等。
3.使用场景
(1)开启多个线程分块下载一个大文件,每个线程只下载固定的一截,最后由另外一个线程来拼接所有的分段。
(2)应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
(3)确保一个计算不会执行,直到所需要的资源被初始化。
4.参考例子
在LeetCode(力扣)中,题目:1114. 按序打印,参考地址(https://leetcode-cn.com/problems/print-in-order/);
题目描述:
我们提供了一个类:
public class Foo {
public void one() { print("one"); }
public void two() { print("two"); }
public void three() { print("three"); }
}
三个不同的线程将会共用一个 Foo 实例。
线程 A 将会调用 one() 方法
线程 B 将会调用 two() 方法
线程 C 将会调用 three() 方法
请设计修改程序,以确保 two() 方法在 one() 方法之后被执行,three() 方法在 two() 方法之后被执行。
解题方法:
这题是典型创造屏障的方法。我们需要构造 2道屏障,second 线程等待 first 屏障,third 线程等待 second 屏障。
countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
涉及api有:
- await():等待
- countDown():减一
代码:
private CountDownLatch countDownLatchA;
private CountDownLatch countDownLatchB;
public Foo() {
countDownLatchA = new CountDownLatch(1);
countDownLatchB = new CountDownLatch(1);
}
public void first(Runnable printFirst) throws InterruptedException {
// printF|irst.run() outputs "first". Do not change or remove this line.
countDownLatchA.countDown();
printFirst.run();
}
public void second(Runnable printSecond) throws InterruptedException {
// printSecond.run() outputs "second". Do not change or remove this line.
countDownLatchA.await();
printSecond.run();
countDownLatchB.countDown();
}
public void third(Runnable printThird) throws InterruptedException {
// printThird.run() outputs "third". Do not change or remove this line.
countDownLatchB.await();
printThird.run();
}
注:
1. latch.countDown(); 建议放到finally语句里。
2. 对这个计数器的操作都是原子操作,同时只能有一个线程去操作这个计数器。