文件目录遍历的并发算法
问题:算出指定目录下文件的大小.
这个是个很简单的问题嘛,直接做个递归就行,上顺序算法:
public long getFileSize(final File file){ if(file.isFile()){ return file.length(); } else{ long total = 0; File files[] = file.listFiles(); if(files == null)return 0; for(File f: files){ total += getFileSize(f); } return total; } }
很简单,一个递归实现,那么现在我们思考并发的算法
并发思路:每次进行递归运算,每次开一个线程去计算当前目录的文件大小,然后进行递归运算
并发代码:
private long getFileSizebf(final ExecutorService service,final File file) throws InterruptedException, ExecutionException, TimeoutException{ if(file.isFile()) return file.length(); else{ final File files[] = file.listFiles(); if(files == null) return 0; else{ long total = 0; final List<Future<Long>> tasks = new ArrayList<Future<Long>>(); for(final File f : files){ tasks.add(service.submit(new Callable<Long>() { @Override public Long call() throws Exception { // TODO Auto-generated method stub
//进行递归,把子目录的文件大小返回
return getFileSizebf(service, f); } })); } for(final Future<Long> fl : tasks){ total += fl.get(); } return total; } } }
看上去没什么问题,我们来实际测试下;
我们看到,调用get从Future中取数据的时候,并没有设置超时,实际运行中发现,当文件夹的目录结构简单,目录树比较浅的时候能够跑出来,但是目录树结构复杂了以后,跑了很久还是跑不出来结果.
分析:我们每个线程会开启新的线程去计算子目录的大小,这个时候,线程会进入等待,等待子线程的返回结果,这个时候就会出现一种情况,假如目录树的结构复杂,那么很多线程会进入等待状态,等待子线程的返回值,但是这些父线程还是占用着线程池,但是子线程请求不到线程池去执行,这个时候就会进入死锁.
如下图:
优化策略:1.既然是线程池的poolsize不够用了,那么我们就增加poolsize的大小嘛,好了,现在我们面对的是一个目录结构不那么复杂的,通过简单地增加poolsize还可以做到,但是非常复杂的话就没办法了.
2.我们之所以会造成死锁,是因为线程在占用线程池的情况下同时在等待子线程的返回结果,
优化版本1:
public class FileOPBX1 implements FileOpI { @Override public long getTotalSizeOfFilesInDir(File f) { long total = 0; final ExecutorService eService = Executors.newFixedThreadPool(100); // TODO Auto-generated method stub final List<File> dirs = new ArrayList<>(); dirs.add(f);//初始化当前文件夹 while (!dirs.isEmpty()) { //每次计算dir里面一集目录下的文件大小以及子目录 final List<Future<SubDirAndSize>> part = new ArrayList<>(); for (final File dir : dirs) { part.add(eService.submit(new Callable<SubDirAndSize>() { @Override public SubDirAndSize call() throws Exception { // TODO Auto-generated method stub return getEveryDir(dir); } })); } dirs.clear();//目录分配任务完毕,清除 //从返回的结果计算文件大小,并且把新的子目录添加到待计算文件夹 for (final Future<SubDirAndSize> subdir : part) { try { final SubDirAndSize sas = subdir.get(); total += sas.size; dirs.addAll(sas.subDirs); } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(dirs); } return total; } //计算当前一级目录下文件的大小以及子目录 private SubDirAndSize getEveryDir(final File f) { long total = 0; final List<File> subDirs = new LinkedList<File>(); if (f.isDirectory()) { final File[] sub = f.listFiles(); if (sub != null) { for (final File dir : sub) { if (dir.isFile()) total += dir.length(); else { subDirs.add(dir); } } } } return new SubDirAndSize(total, subDirs); } public class SubDirAndSize { final public long size; final public List<File> subDirs; public SubDirAndSize(final long totalsize, final List<File> thesubDir) { size = totalsize; subDirs = Collections.unmodifiableList(thesubDir); } } }
这个时候我们看结果:
运行花费时间9688
串行执行结果:19563974028
运行花费时间3230
并行执行结果:19563974028