CountDownLatch是一个高级的线程同步工具,可以理解为一个闸门,先运行一些线程,计算出一些结果,满足条件后,开闸,然后另一些线程启动。换句话说,它将多个线程分为2类,一类开闸前运行,一类开闸后运行。
对于开闸后运行的线程,调用await()方法后,进入等待状态,等待开闸。它内部有一个计数器,初始状态计数器为用户设定的正整数,当开闸前某个线程运行完毕后,调用它的countDown()方法将计数器减1,当计数器为0时,开闸。
典型应用:统计硬盘上文件的数目。针对硬盘的每一个分区,例如C盘、D盘、E盘等,单独开一个线程统计该分区内的文件数。另设一个线程对所有分区的文件数汇总,显示给用户,此线程运行在开闸后,必须等每个分区都统计完毕之后,开闸,汇总报告给用户。countDown()方法最好不要由开闸前的线程直接调用,而应该由汇总线程对其进行封装,开闸前的线程之调用封装后的方法,CountDownLatch尽量对开闸前的线程透明。
/** * 负责将多线程查找的各分区文件数目合并汇总, * 打印出硬盘上存在的总文件数 */ class CombineResult implements Runnable { private CountDownLatch latch; private long fileCount; public CombineResult(int n) { latch = new CountDownLatch(n); fileCount = 0; } public synchronized void commitResult(long count) { fileCount += count; latch.countDown(); } @Override public void run() { try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("\n硬盘上共有" + fileCount + "个文件"); } } /** * 统计一个硬盘分区内的文件数 */ class CountFilesInPartition implements Runnable{ private CombineResult result; private Path partition; //需要Java 1.7支持 private long count; public CountFilesInPartition(CombineResult result, Path partition) { this.result = result; this.partition = partition; count = 0; } @Override public void run() { long n; n = countFiles(partition); result.commitResult(n); } private long countFiles(Path p) { if(Files.isDirectory(p)) { try(DirectoryStream<Path> paths = Files.newDirectoryStream(p)) { for(Path path:paths) { count = countFiles(path); } } catch (IOException e) { } }else { count++; } return count; } } /** * 进度条 */ class ProgressBar extends Thread{ private int interval; //显示进度的时间间隔,单位是毫秒 public ProgressBar(int interval) { this.interval = interval; setDaemon(true); } @Override public void run() { System.out.print("正在进行中"); while(true) { try { Thread.sleep(interval); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.print(" ."); } } } public class CountFile { public static void main(String[] args) { int ptCount = getPartitionCount(); CombineResult result = new CombineResult(ptCount); new Thread(result).start(); Iterable<Path> partitions = FileSystems.getDefault().getRootDirectories(); for(Path p:partitions) { new Thread(new CountFilesInPartition(result, p)).start(); } new ProgressBar(1000).start(); } private static int getPartitionCount() { Iterable<Path> partitions = FileSystems.getDefault().getRootDirectories(); int n = 0; for(Path p:partitions) { n++; } return n; } }
以下是不使用CountDownLatch,而是直接使用Lock和Condition的实现,做一个对比
public class CountFiles { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub long start = System.currentTimeMillis(); Iterable<Path> drivers = FileSystems.getDefault().getRootDirectories(); int n = 0; for(Path driver:drivers) { n++; } Result r = new Result(n); for(Path driver:drivers) { new MyThread(driver, r).start(); } System.out.println(r.getFileCount()); System.out.println(System.currentTimeMillis()-start); } private static class MyThread extends Thread { private Path driver; private long count; private Result result; public MyThread(Path driver, Result r) { super(); this.driver = driver; count = 0; result = r; } @Override public void run() { countFiles(driver); result.addFileCount(count); result.cutThreadCount(); } private void countFiles(Path path) { if(Files.isDirectory(path)) { DirectoryStream<Path> paths; try { paths = Files.newDirectoryStream(path); for(Path p:paths) { countFiles(p); } } catch (IOException e) { } }else { count++; } } } private static class Result { private long fileCount = 0; private int threadCount = 0; public Result(int n) { threadCount = n; } public synchronized void addFileCount(long n) { fileCount += n; } public synchronized long getFileCount() { while(threadCount >0) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return fileCount; } public synchronized void cutThreadCount() { threadCount--; notifyAll(); } } }