多线程分配线程的实现方案:CountDownLatch类
需求:假如我们本地有4个文件需要解析,每个文件的内容为20万行。为了提高效率我们要创建4个线程进行处理。等4个线程处理完,要在文件日志表中记录处理状态。
一般的的解决方法是使用join,join用于让当前执行线程等待join线程执行结束。其实现原理是不停检查join线程是否存活,如果join线程存活则让当前线程永远wait。直到join线程中止后,线程的this.notifyAll会被调用。
但是DK1.5之后的并发包中提供的CountDownLatch也可以实现join的这个功能,并且比join的功能更多。
1 public class CountDownLatchTest { 2 3 static CountDownLatch c = new CountDownLatch(4); 4 /**线程记录数,当线程开始调用时主线程阻塞,每当一个线程结束时调用countDown()方法,线程记录数减一,当线程记录数为0时,主线程恢复调用。***/ 5 6 public static void main(String[] args) throws InterruptedException { 7 new Thread(new Runnable() { 8 @Override 9 public void run() { 10 System.out.println(1); 11 c.countDown();//记录数减一,3 12 System.out.println(2); 13 c.countDown();//记录数减一,2 14 System.out.println(3); 15 c.countDown();//记录数减一,1 16 System.out.println(4); 17 c.countDown();//记录数减一,0 18 } 19 }).start(); 20 21 c.await(); 22 System.out.println("主线程调用");//开始调用日志记录 23 } 24 25 }
CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完成,这里就传入N。
当我们调用一次CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await会阻塞当前线程,直到N变成零。由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是1个线程里的N个执行步骤。用在多个线程时,你只需要把这个CountDownLatch的引用传递到线程里。
1 /** 2 * 单个文件处理入库 3 * @author yanjp 4 * 5 */ 6 class Worker extends Thread{ 7 private DyFile dyFile; 8 private CountDownLatch ct; 9 private AnalyzeFileService analyzeFileService; 10 public Worker(AnalyzeFileService analyzeFileService ,DyFile dyFile,CountDownLatch ct){ 11 this.dyFile=dyFile; 12 this.ct=ct; 13 this.analyzeFileService=analyzeFileService; 14 } 15 @Override 16 public void run() { 17 // TODO Auto-generated method stub 18 try { 19 logger.info("begin入库处理文件:"+dyFile.getFileName()); 20 //合适的处理器,处理文件. 21 analyzeFileService.analyze(dyFile); 22 logger.info("end入库处理文件:"+dyFile.getFileName()); 23 } catch (Exception e) { 24 // TODO Auto-generated catch block 25 logger.error("单个文件入库处理异常",e); 26 }finally{ 27 //完成一件任务 28 ct.countDown(); 29 } 30 } 31 32 }
如果有某个解析sheet的线程处理的比较慢,我们不可能让主线程一直等待,所以我们可以使用另外一个带指定时间的await方法,await(long time, TimeUnit unit): 这个方法等待特定时间后,就会不再阻塞当前线程。join也有类似的方法。
注意:计数器必须大于等于0,只是等于0时候,计数器就是零,调用await方法时不会阻塞当前线程。CountDownLatch不可能重新初始化或者修改CountDownLatch对象的内部计数器的值。一个线程调用countDown方法 happen-before 另外一个线程调用await方法。
CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。
参考:
http://ifeve.com/talk-concurrency-countdownlatch/