1 CounDownLatch介绍
CountDownLatch是java 1.5新引入的一个同步工具类,常用场景为:一个(或者多个)线程等待另外N个线程完成某个事情之后才能执行。是一个非常有用的线程同步类。
其API有:
//递减锁存器的计数值,如果计数达到0,就释放所有等待的线程。如果计数大于0,计数减1
public void countDown()
//等待锁存器的计数值减为0
public void await()
2实际例子
2.1 场景一
考虑这样一个实际的问题:公司组织一个会议,与会者10名,只有当10名与会者全部到齐,会议才能开始。
这个场景,用CountDownLatch可以很方便实现:
这public class TestCountDownLatch {
private static final int PEOPLE_NUM = 10;
public static CountDownLatch latch = new CountDownLatch(PEOPLE_NUM);
private static AtomicInteger index = new AtomicInteger(0);
public static class People implements Runnable {
private CountDownLatch latch;
private int index;
public People(CountDownLatch latch, int index) {
this.latch = latch;
this.index = index;
}
@Override
public void run() {
latch.countDown();
System.out.println("People index = " + index + " arrival.");
}
}
public static class Conference implements Runnable {
private CountDownLatch latch;
public Conference(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
System.out.println("Conference begin wait all people arrival");
try {
latch.await();
} catch (InterruptedException e) {
}
System.out.println("Conference started");
}
}
public static void main(String[] args) {
Thread conferenceThread = new Thread(new Conference(latch));
conferenceThread.start();
for (int i = 0; i < PEOPLE_NUM; i++) {
Thread thread = new Thread(new People(latch, index.addAndGet(1)));
thread.start();
}
}
}
运行结果:
Conference begin wait all people arrival
People index = 1 arrival.
People index = 2 arrival.
People index = 3 arrival.
People index = 4 arrival.
People index = 5 arrival.
People index = 6 arrival.
People index = 7 arrival.
People index = 8 arrival.
People index = 9 arrival.
People index = 10 arrival.
Conference started
从运行结果可以看到调用await以后,Conference会等待其余10个线程调用countDown,当计数为0时,Conference会从 await点继续向下执行。
Android系统中很多地方都用到了这样的设计。
比如SharedPreferences.java中:
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
.....省略无关代码
public void setDiskWriteResult(boolean result) {
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
实现commit()同步方法的时候,会调用await等待写执行,写入磁盘的操作完成时,会调用countDown(),这样就保证commit()返回的时候,数据已经被写入磁盘。从而保证数据不会出现问题。
2.2 场景二
考虑这样一个场景:Android执行代码时,如何将一段异步代码转为同步代码?
这个场景,用CountDownLatch也能很好的予以解决。Android插件花框架Replugin的一段代码就是这个场景的一个实现:
public class ThreadUtils {
private static Handler sHandler = new Handler(Looper.getMainLooper());
/**
* 确保一定在主线程中使用
* <p>
* 若当前处于主线程,则直接调用。若当前处于其它线程,则Post到主线程后等待结果
*
* @param callable Callable对象
* @param wait 最长等待主线程的时间
* @param <T> 任何Object子类均可以
* @return 主线程执行完方法后,返回的结果
*/
public static <T> T syncToMainThread(final Callable<T> callable, int wait) throws Throwable {
if (sHandler.getLooper() == Looper.myLooper()) {
// 已在UI线程中使用,则直接调用它
return callable.call();
} else {
// 不在UI线程,需尝试Post到UI线程并等待
return syncToMainThreadByOthers(callable, wait);
}
}
private static <T> T syncToMainThreadByOthers(final Callable<T> callable, int wait) throws Throwable {
final AtomicReference<T> result = new AtomicReference<>();
final AtomicReference<Throwable> ex = new AtomicReference<>();
// 异步转同步
final CountDownLatch latch = new CountDownLatch(1);
// 必须在主线程进行
sHandler.post(new Runnable() {
@Override
public void run() {
try {
result.set(callable.call());
} catch (Throwable e) {
ex.set(e);
}
latch.countDown();
}
});
try {
latch.await(wait, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// ignore
}
// 若方法体有异常?直接抛出
Throwable exo = ex.get();
if (exo != null) {
throw exo;
}
// 没有问题?则直接返回结果
return result.get();
}
}
syncToMainThreadByOthers中的实现,可以看到如何将一段代码由异步转为同步来执行。
2.3 场景三
以上都是一个线程等待N个线程执行完毕。CountDownLatch也可以适用于多个线程等待N个线程的场景。
比如小组的员工出差,需要组长审批,总监审批完毕以后,才可以出差。用CountDownLatch也能很好的予以解决:
代码如下:
public class TestCountDownLatch {
private static final int ROUTINE_NUM = 2;
private static final int STAFF_NUM = 10;
public static CountDownLatch latch = new CountDownLatch(ROUTINE_NUM);
private static AtomicInteger index = new AtomicInteger(0);
public static class Manager implements Runnable {
private CountDownLatch latch;
public Manager(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
latch.countDown();
System.out.println("Manager agree the business trip");
}
}
public static class Leader implements Runnable {
private CountDownLatch latch;
public Leader(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
latch.countDown();
System.out.println("Leader agree the business trip");
}
}
public static class Staff implements Runnable {
private CountDownLatch latch;
private int index;
public Staff(CountDownLatch latch, int index) {
this.latch = latch;
this.index = index;
}
@Override
public void run() {
System.out.println("Staff index = " + index + " begin wait business trip");
try {
latch.await();
} catch (InterruptedException e) {
}
System.out.println("Staff index = " + index + " can go now");
}
}
public static void main(String[] args) {
for (int i = 0; i < STAFF_NUM; i++) {
Thread thread = new Thread(new Staff(latch, index.addAndGet(1)));
thread.start();
}
Thread manager = new Thread(new Manager(latch));
manager.start();
Thread leader = new Thread(new Leader(latch));
leader.start();
}
}
测试结果如下:
Staff index = 1 begin wait business trip
Staff index = 3 begin wait business trip
Staff index = 4 begin wait business trip
Staff index = 2 begin wait business trip
Staff index = 5 begin wait business trip
Staff index = 6 begin wait business trip
Staff index = 7 begin wait business trip
Staff index = 8 begin wait business trip
Staff index = 9 begin wait business trip
Staff index = 10 begin wait business trip
Manager agree the business trip
Leader agree the business trip
Staff index = 3 can go now
Staff index = 1 can go now
Staff index = 4 can go now
Staff index = 5 can go now
Staff index = 2 can go now
Staff index = 7 can go now
Staff index = 8 can go now
Staff index = 9 can go now
Staff index = 10 can go now
Staff index = 6 can go now
可以看到Staff 10个线程等待Manager/Leader两个线程审批出差申请,两个审批步骤通过以后,10个等待的Staff员工就可以出差了。