Java线程CountDownLatch实例源码讲解



Java线程CountDownLatch实例源码讲解。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();
}
}
}