Java并发工具类-CountDownLatch
示例场景
一天程序员小开在做一个需求,这个需求的内容是需要它查询一个订单的内容。但是这个订单在哪个平台是无法确认的,查询订单需要使用订单号去不同的平台去查。小开收到任务后,心想这不是很简单吗?一顿CV操作后完成了这个需求。
public class OrderQueryApp {
public static void main(String[] args) throws InterruptedException {
//未优化前
queryOrder();
}
/**
* 未优化前代码
*
* @throws InterruptedException
*/
public static void queryOrder() throws InterruptedException {
OrderQueryApp app = new OrderQueryApp();
long t1 = System.currentTimeMillis();
app.queryOrderFromTb();
app.queryOrderFromJd();
long t2 = System.currentTimeMillis();
System.out.printf("查询总共耗时:[%d]sn", (t2 - t1) / 1000);
}
public void queryOrderFromTb() throws InterruptedException {
//省略实现
System.out.println("从淘宝查询订单....");
Thread.sleep(2000);
}
public void queryOrderFromJd() throws InterruptedException {
//省略实现
System.out.println("从京东查询订单....");
Thread.sleep(3000);
}
}
问题
小开在完成任务后把代码提交上去了,组长看完代码后找到小开进行了如下的对话。
-
组长
:你这个任务是完成了,但是我觉得可以优化一下。 -
小开
:哪里需要优化了?我觉得没问题。 -
组长
:这个需求是实现了,但是这个查询时间太长了。如果使用多线程分别从不同平台查询订单,那这个时间是不是就缩短了呢? -
小开
:我也想过使用多线程来实现,但是要实现两个查询任务完成之后再执行主线程太麻烦了。 -
组长
:我知道你的困惑了,你可以去看看CountDownLatch
它或许能解决你的问题。
CountDownLatch
是什么
CountDownLatch
是JDK1.5
提供的一个同步辅助类,在完成一组其他线程操作之前,它允许一个或多个线程一直等待。简单的说它就是一个计数器,而计数器的总数就是线程的数量,每当线程完成一次任务则把计数器的值减一,直到所有线程的任务都完成了,计数器将归0,等待在这个计数器上的线程则结束等待继续执行。
如何使用
它的主要方法有以下几个:
-
构造方法 public CountDownLatch(int count)
:该方法只有一个参数就是初始计数大小,在初次创建之后无法再次修改。 -
public void countDown()
:递减计数器方法。当计数器中的值变为0时,则释放所有等待的线程。 -
public void await() throws InterruptedException
:在计数器没有变为0时,使当前调用线程等待在该CountDownLatch
实例上。除非发生了等待超时或者中断。
修改实现
public class OrderQueryApp {
public static void main(String[] args) throws InterruptedException {
//未优化前
queryOrder();
//优化后的代码
System.out.println("========优化后代码============");
queryOrder2();
}
/**
* 未优化前代码
*
* @throws InterruptedException
*/
public static void queryOrder() throws InterruptedException {
OrderQueryApp app = new OrderQueryApp();
long t1 = System.currentTimeMillis();
app.queryOrderFromTb();
app.queryOrderFromJd();
long t2 = System.currentTimeMillis();
System.out.printf("查询总共耗时:[%d]sn", (t2 - t1) / 1000);
}
/**
* 优化后代码
*/
public static void queryOrder2() throws InterruptedException {
OrderQueryApp app = new OrderQueryApp();
long t1 = System.currentTimeMillis();
//创建CountDownLatch
CountDownLatch cdl = new CountDownLatch(2);
//从淘宝查询订单
new Thread(() -> {
try {
app.queryOrderFromTb();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
cdl.countDown();
}
}).start();
//从京东查询订单
new Thread(() -> {
try {
app.queryOrderFromJd();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
cdl.countDown();
}
}).start();
//主线程等待
cdl.await();
long t2 = System.currentTimeMillis();
System.out.printf("查询总共耗时:[%d]sn", (t2 - t1) / 1000);
}
public void queryOrderFromTb() throws InterruptedException {
//省略实现
System.out.println("从淘宝查询订单....");
Thread.sleep(2000);
}
public void queryOrderFromJd() throws InterruptedException {
//省略实现
System.out.println("从京东查询订单....");
Thread.sleep(3000);
}
}
修改后的代码如上所示,上面开启两个线程去执行查询订单任务,这将原本串行的任务变成并行了。而并行带来的主要问题就是主线程在子线程未完成之前需要进入等待,而所有子线程完成了之后需要唤醒主线程继续执行。而上面这些问题,CountDownLatch
就能很容易解决。
使用注意事项
-
countDown()
放在finally
中使用
对于countDown()
方法的调用最后是放在finally
代码块中。如果子线程在执行过程中出现了异常,会导致countDown()
方法不会执行。这将导致计数器无法变成0从而导致主线程一直等待在countDown()
中无法正常执行,最后导致死锁的发生。所以为了避免死锁发生,推荐将countDown()
放在finally
中执行,这样即使发生了异常countDown()
方法还是能正常被调用。
原文始发于微信公众号(一只菜鸟程序员):Java并发工具类-CountDownLatch
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/72924.html