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里。。