問題:算出指定目錄下文件的大小.
這個是個很簡單的問題嘛,直接做個遞歸就行,上順序算法:
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
