java并发之CountDownLatch

一、简介

CountDownlatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

它本身而言是Java并发包中非常有用的一个类,它可以让某些任务完成以后再继续运行下面的内容,每个任务本身执行完毕后让计数器减一,直到计数器清零后,以下的内容才可以继续运行,否则将阻塞等待。

二、应用场景

想了一下,这个场景非常适合用于项目中这样的场景: 我们有个项目,它需要三个第三方的API,并把结果拿到,在一个线程中顺序去拿结果没有问题,但是这里这三个任务是非常耗时的操作,如果顺序获取性能非常差,因此可以考虑用三个线程,当三个线程拿到结果后才继续主线程的工作,等三个线程运行结束后,由主线程去取子线程运行的结果。 这里有个很重要的前提:我们的系统运行在4个cpu的server上,这样多线程才能体现性能,JVM会分配这些线程尽量运行在不同的cpu上。

CountDownLatch有以下基本方法:

1)await(),阻塞等待,直到计数器清零

2)await(int timeout, TimeUnit unit),使线程阻塞,除非被中断或者超过等待的最大时间。如果达到计数器清零,则await返回true,如果等待超过了最大的等待时间,则返回false。

3)countDown(),计数器减一,当计数器清零时,await的线程被唤醒,线程继续执行

4)getCount(),获取当前计数器的大小

代码示例

现在模拟一个业务场景,就是页面上的列表数据导出,通常的做法是根据查询条件从数据库,然后把查询的数据再逐个写到excel文件里,正常情况这样做是没问题的,但是如果数据量很大,还在一个线程里操作的话,将会非常耗时。这时我们可以根据实际情况,比方说新建三个线程,这三个线程把整个要处理的数据拆分成三段分别同时处理,然后主线程等这三个线程都处理完了再进行整合这三部分的操作。

那么问题来了,怎么才能确保主线程始终能等这三个业务线程执行完了再执行呢?

 1 public class Test {
 2 
 3     public static void main(String[] args) throws IOException {
 4         // 模拟数据库资源
 5         CopyOnWriteArrayList<Integer> ziyuan = new CopyOnWriteArrayList<Integer>();
 6         for (int i = 1; i < 10; i++) {
 7             ziyuan.add(i);
 8         }
 9         ExecutorService executorService = Executors.newCachedThreadPool();
10         // 标记一共有ziyuan.size()/3个线程需要等待
11         CountDownLatch cLatch = new CountDownLatch(ziyuan.size()/3);
12         // 开启三个线程
13         for (int i = 0; i < ziyuan.size()/3; i++) {
14             executorService.execute(new TaskRunn(ziyuan,cLatch, 3*i));
15         }
16         // 关闭线程池
17         executorService.shutdown();
18         // 主线程等待
19         try {
20             cLatch.await();
21         } catch (InterruptedException e) {
22             e.printStackTrace();
23         }
24         System.out.println("我是主线程要操作的内容,必须确保我在其它三个线程执行完后最后打印。。。");
25     }
26 }
27 
28 class TaskRunn implements Runnable{
29 
30     private CopyOnWriteArrayList<Integer> ziyuan;
31     private CountDownLatch cLatch;
32     private int c;
33     public TaskRunn(CopyOnWriteArrayList<Integer> ziyuan,CountDownLatch cLatch,int c) {
34         this.ziyuan = ziyuan;
35         this.cLatch = cLatch;
36         this.c = c;
37     }
38     
39     @Override
40     public void run() {
41         System.out.println(Thread.currentThread().getName()+"begin...");
42         for (int i = c; i < ziyuan.size()/3+c; i++) {
43             System.out.println(Thread.currentThread().getName()+"//"+ziyuan.get(i)+"//CountDownLatch:"+cLatch.getCount());
44             try {
45                 Thread.sleep(new Random().nextInt(4)*1000);// 模拟业务耗时
46             } catch (InterruptedException e) {
47                 e.printStackTrace();
48             }
49         }
50         System.out.println(Thread.currentThread().getName()+"end...");
51         // 每个业务线程执行完后会减去1
52         cLatch.countDown();
53     }
54     
55 }

如果把上面代码红色字体部分注释掉,那结果就不会这样了。。。

注意点

在实际开发中,为了防止发生异常时cLatch.countDown()没有执行到,所以最好把它放到finally里。。

 

posted @ 2017-04-13 19:36  夏威夷8080  阅读(670)  评论(0编辑  收藏  举报