前言:
在开发过程中,可能会遇到这样一种情况。当要进行事件A,B。A的可执行前提是:B的某一部分必须先完成。这种时候,如果采用join的话,必须等到B执行完,A才能进行。从事件安全上来说虽然没有什么问题,但是,从效率上来讲,显然是不能接受的。
举个例子:小明和他女朋友小红,准备周末在家做一顿大餐。他们买了大虾、里脊和西兰花。小明帮忙洗菜(事件A)。小红掌勺(事件B)。小红要做菜的前提是:大虾、里脊和西兰花中的至少一种已经准备好。小红可以等到小明全部都洗完了,再开始做菜(可以用join来实现)。当然,如果大虾弄好了,小红可以马上开始做菜(CountDownLatch来实现),这样就更快了。而小明,可以在小红做菜的时候,继续,洗里脊和西兰花。
准备:
CountDownLatch在java.util.concurrent下提供。此次使用下面三个方法
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
count控制初始化线程的数量
public void countDown() {
sync.releaseShared(1);
}
此方法用来将计数器减一
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
此方法当CountDownLatch中的计数器为0时才会执行
上测试代码
@Slf4j
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(1);
XiaoMing xiaoMing = new XiaoMing(cdl);
XiaoHong xiaoHong = new XiaoHong();
log.info("做饭");
xiaoMing.start();
cdl.await();
xiaoHong.start();
}
}
@Slf4j
public class XiaoHong extends Thread {
@Override
public void run() {
log.info("小红开始做菜");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("小红做菜结束");
log.info("开饭");
}
}
@Slf4j
public class XiaoMing extends Thread {
CountDownLatch cdl;
XiaoMing(CountDownLatch cdl){
this.cdl = cdl;
}
@Override
public void run() {
try {
//处理具体业务逻辑
log.info("小明开始处理:大虾");
Thread.sleep(1000);
log.info("小明将 大虾 处理完");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
cdl.countDown();
}
try {
log.info("小明开始处理:里脊");
Thread.sleep(1000);
log.info("小明将 里脊 处理完");
log.info("小明开始处理:西兰花");
Thread.sleep(1000);
log.info("小明将 西兰花 处理完");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
最终结果:
从测试结果我们可以看到,到小明处理完大虾以后,小红就开始做菜了。(小明开始处理里脊和小红开始做菜,会有交换的情况)。通过,这个例子相信可以,理解闭锁的灵活之处。
CountDownLatch 使用的注意点:
1、只有当count为0时,await之后的程序才够执行。
2、countDown必须写在finally中,防止发生异程常时,导致程序死锁。