JDK的current包下提供了一系列的多线程相关的工具类,很多类都是出自Doug Lea之手, 正是这些大神的无私奉献才有了java能够在20年的时间里不断的成熟,不断的发展,才有了当今丰富多彩的java体系的开源世界。
其中的CountDownLatch和CyclicBarrier是在JDK5之后加入的,提供了一些线程之间协作的手段。在没有这些类之前,要完成线程之间的协作只有通过join方法,比较容易出错,编写多线程的代码是件比较痛苦的事情。有了这些多线程的工具类后,多线程的协助容易了太多。
项目中有遇到一个场景,之前串行化的动作,由于各个串行化动作之间没有太多的关联,而如果一个一个的进行操作,则耗时会比较多,对用户造成的延时就比较长,为了解决这个问题,项目上进行了分析后决定将串行化的动作改为多线程并行操作来实现,从而提供系统的响应速度,这样就涉及到线程之间的协助。
使用CountDownLatch就非常适合
首先在主线程中创建一个CountDownLatch然后在各个任务线程中在完成了相关的操作后,将latch减1,当所有的任务都已经完成了之后主线程会从await中醒过来。如下所示
final CountDownLatch cdl = new CountDownLatch(3); final Map<String, Object> result = new HashMap<String, Object>(); ExecutorService exec = Executors.newCachedThreadPool(); exec.submit(new Runnable() { @Override public void run() { // TODO Auto-generated method stub // TODO Auto-generated method stub try { Thread.currentThread().sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } result.put("TASK1", "Task1Result"); System.out.println("Task1 End"); cdl.countDown(); } }); exec.submit(new Runnable() { @Override public void run() { // TODO Auto-generated method stub // TODO Auto-generated method stub try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } result.put("TASK2", "Task2Result"); System.out.println("Task2 End"); cdl.countDown(); } }); exec.submit(new Runnable() { @Override public void run() { // TODO Auto-generated method stub try { Thread.currentThread().sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } result.put("TASK3", "Task3Result"); System.out.println("Task3 End"); cdl.countDown(); } }); System.out.println("MainThread Will Wait"); cdl.await(10, TimeUnit.SECONDS); System.out.println("All Task End Start next process"); StringBuilder resultStr = new StringBuilder(); //结束了之后,读取result的数据 继续后续的动作 for (Entry<String, Object> entry : result.entrySet()) { resultStr.append(entry.getValue()); } System.out.println(resultStr); 结果如下MainThread Will Wait Task3 End Task2 End Task1 End All Task End Start next process Task1ResultTask2ResultTask3Result 这样可以将串行化的动作修改为并行化的动作。。
2. CyclicBarrier的使用
CyclicBarrier的使用和CountDownLatch相反,CyclicBarrier主要是作为一个统一的起始点,当每个线程都准备好了之后,才开始统一的动作,如要进行赛跑比赛,需要每个人都准备好了之后才可以开始,这样就需要一个协调者来对各个线程进行协调,如下所示
final CyclicBarrier cb = new CyclicBarrier(3, new Runnable(){ @Override public void run() { // TODO Auto-generated method stub System.out.println("All waiting is finished"); } }); ExecutorService exec = Executors.newCachedThreadPool(); for (int i = 0; i < 3; i++) { exec.execute(new Runnable(){ @Override public void run() { // TODO Auto-generated method stub System.out.println(Thread.currentThread().getName() + "Waitting Number" + cb.getNumberWaiting()); try { System.out.println(Thread.currentThread().getName() + " Begin Preparing"); Thread.sleep(new Random().nextInt(3000)); cb.await(10, TimeUnit.SECONDS); System.out.println(Thread.currentThread().getName() + "Start Running "); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " End Running"); } }); } System.out.println("Main Thread Wait"); exec.shutdown();运行结果如下pool-1-thread-1Waitting Number0pool-1-thread-2Waitting Number0Main Thread Waitpool-1-thread-3Waitting Number0pool-1-thread-3 Begin Preparingpool-1-thread-2 Begin Preparingpool-1-thread-1 Begin PreparingAll waiting is finishedpool-1-thread-3Start Running pool-1-thread-3 End Runningpool-1-thread-1Start Running pool-1-thread-1 End Runningpool-1-thread-2Start Running pool-1-thread-2 End Running这样所有的线程就会有一个初始的起始点开始运行。对于解决特定场景的问题还是比较有用的,,
个人感觉CountDownLatch在编码中使用到的机会更多。。