多執行緒掃描資料夾耗時方法分析

Brewin發表於2020-09-11

在這裡Java的方法中有執行緒遞迴,不懂得用什麼方法求執行時間遇到一個有趣的問題,多執行緒掃描資料夾求執行時間。一般這種掃描資料夾耗時好像都是用的遞迴遍歷一下進行計時,頭一次看到這種一個資料夾一個執行緒的,這裡按照原問題樓主的想法試著解決一下。

剛開始我是採用靜態變數,共享總耗時,把每個run方法耗時都加上。

package com.brewin.codetuning.test;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ThreadDemo02
{
    public static void main(String args[]) {
        File file=new File("D:\\AA");
        MyThread mt1 = new MyThread("執行緒1",file);
        mt1.start();
    }
}

class MyThread extends Thread
{
    private String name;
    private File file;
    public static int sum;
    static {
        sum = 0;
    }

    public MyThread(String name,File file) {
        this.name = name;
        this.file=file;
    }

    @Override
    public void run() {
        long startTime =  System.currentTimeMillis();
        File[] files=file.listFiles();
        if(files!=null &&files.length>0) {
            for(File f:files) {
                if(f.isDirectory()) {
                    new MyThread(f.getName(),f).start();
                }
//                列印檔名不方便看最後的耗時列印,註釋掉
//                else {
//                    System.out.println(Thread.currentThread().getName()+":"+f);
//                }
            }
        }
//        try {
//            Thread.sleep(5000);
//        }
//        catch (InterruptedException e) {
//            // TODO Auto-generated catch block
//            e.printStackTrace();
//        }
        long endTime =  System.currentTimeMillis();
        long usedTime = (endTime-startTime);
        sum+=usedTime;
        System.out.println(name + " 執行結束,耗時: "+usedTime+" ms,sum現為:"  +MyThread.sum+" ms");
    }

}

控制檯列印的什麼呢?下面擷取最後一部分

storage 執行結束,耗時: 0 ms,sum現為:3156 ms
x509 執行結束,耗時: 1 ms,sum現為:3157 ms
implementations 執行結束,耗時: 1 ms,sum現為:3158 ms
implementations 執行結束,耗時: 1 ms,sum現為:3159 ms

但是實際並沒有用到3秒多,很快就結束了。

下面是加了Thread.sleep(5000)的結果,更明顯一些:

xs 執行結束,耗時: 5004 ms,sum現為:2611580 ms
implementations 執行結束,耗時: 5001 ms,sum現為:2606576 ms
traversers 執行結束,耗時: 5001 ms,sum現為:2616581 ms

多執行緒是併發執行,所以這個思路是錯誤的,不能計算他們的總和,而是要計算他們的最開始的時間和最後的時間。改變思路,在最開始時初始化一個開始時間,後面每次執行緒結束後都更新一下最後時間,求他們之間的差值就可以了。

package com.brewin.codetuning.test;

import java.io.File;

public class ThreadDemo02
{
    public static void main(String args[]) {
        File file=new File("D:\\AA");
        MyThread mt1 = new MyThread("執行緒1",file);
        mt1.start();
    }
}

class MyThread extends Thread
{
    private static long startTime;
    private String name;
    private File file;

    static {
        startTime =  System.currentTimeMillis();
    }

    public MyThread(String name,File file) {
        this.name = name;
        this.file=file;
    }

    @Override
    public void run() {
        File[] files=file.listFiles();
        if(files!=null &&files.length>0) {
            for(File f:files) {
                if(f.isDirectory()) {
                    new MyThread(f.getName(),f).start();
                }
//                列印檔名不方便看最後的耗時列印,註釋掉
//                }else {
//                    System.out.println(Thread.currentThread().getName()+":"+f);
//                }
            }
        }
        long usedTime = System.currentTimeMillis()-startTime;
        System.out.println(name + " 執行結束,已耗時: "+usedTime+" ms");
    }
}

控制檯最後一段列印如下:

io 執行結束,已耗時: 164 ms
proxy 執行結束,已耗時: 164 ms
dom 執行結束,已耗時: 164 ms
serialize 執行結束,已耗時: 165 ms
fsm 執行結束,已耗時: 165 ms
rmi 執行結束,已耗時: 166 ms
spi 執行結束,已耗時: 166 ms
tree 執行結束,已耗時: 166 ms
graph 執行結束,已耗時: 166 ms
util 執行結束,已耗時: 166 ms
utils 執行結束,已耗時: 167 ms
protocol 執行結束,已耗時: 167 ms
implementations 執行結束,已耗時: 167 ms
http 執行結束,已耗時: 167 ms
content 執行結束,已耗時: 167 ms
resolver 執行結束,已耗時: 167 ms
util 執行結束,已耗時: 168 ms
identity 執行結束,已耗時: 168 ms
keyvalues 執行結束,已耗時: 168 ms
x509 執行結束,已耗時: 169 ms
xs 執行結束,已耗時: 169 ms
serializer 執行結束,已耗時: 169 ms
util 執行結束,已耗時: 169 ms
xs 執行結束,已耗時: 169 ms
util 執行結束,已耗時: 169 ms
validation 執行結束,已耗時: 170 ms
undo 執行結束,已耗時: 170 ms
traversers 執行結束,已耗時: 170 ms
util 執行結束,已耗時: 170 ms
namingutil 執行結束,已耗時: 170 ms
exceptions 執行結束,已耗時: 170 ms
CORBA 執行結束,已耗時: 171 ms
xpath 執行結束,已耗時: 171 ms
transforms 執行結束,已耗時: 171 ms
utils 執行結束,已耗時: 171 ms
params 執行結束,已耗時: 171 ms
encryption 執行結束,已耗時: 171 ms
signature 執行結束,已耗時: 171 ms
output 執行結束,已耗時: 171 ms
implementations 執行結束,已耗時: 172 ms
closure 執行結束,已耗時: 172 ms
concurrent 執行結束,已耗時: 172 ms
orbutil 執行結束,已耗時: 172 ms
helper 執行結束,已耗時: 172 ms
keyresolver 執行結束,已耗時: 172 ms
storage 執行結束,已耗時: 172 ms
implementations 執行結束,已耗時: 173 ms
implementations 執行結束,已耗時: 173 ms
resolver 執行結束,已耗時: 173 ms
implementations 執行結束,已耗時: 173 ms
models 執行結束,已耗時: 174 ms
implementations 執行結束,已耗時: 174 ms
opti 執行結束,已耗時: 174 ms
text 執行結束,已耗時: 174 ms
rtf 執行結束,已耗時: 174 ms
utils 執行結束,已耗時: 175 ms
giopmsgheaders 執行結束,已耗時: 175 ms
reference 執行結束,已耗時: 175 ms
regex 執行結束,已耗時: 175 ms
threadpool 執行結束,已耗時: 175 ms
res 執行結束,已耗時: 176 ms
html 執行結束,已耗時: 177 ms
parser 執行結束,已耗時: 177 ms

看到原問題樓主有提到CountDownLatch ,找來文章看了一下,不是很適合這裡,CountDownLatch計數器的初始大小要跟任務數的大小一致(跟執行緒數無關),每執行一次任務,計數器減一(countDown),await()方法會一直阻塞主執行緒,直到計數器的值減為0,才會釋放鎖,如此便可以達到確保所有任務都完成才繼續下一步的效果。

但是本文場景是不斷遞迴new出新執行緒,並且本文場景中要求任務數和執行緒數是相等的,無法確定任務數,就沒辦法初始化CountDownLatch 大小。

想要用CountDownLatch 的話只能加個統計方法,獲得資料夾數量,如下:

package com.brewin.codetuning.test;

import java.io.File;
import java.util.concurrent.CountDownLatch;

public class ThreadDemo02
{
    static int count;
    static {
        count = 0;
    }
    public static void main(String args[]) {
        File file = new File("D:\\AA");
        countNumberOfFolders(file);
        long startTime = System.currentTimeMillis();
        MyThread mt1 = new MyThread("執行緒1", file);
        mt1.start();
        try {
            MyThread.latch.await();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        long usedTime = System.currentTimeMillis() - startTime;
        System.out.println("耗時: " + usedTime + " ms");

    }

    /**
     *  統計資料夾數量
     *  @param f    
     * @exception/throws [違例型別] [違例說明]
     * @see [類、類#方法、類#成員]
     */
    public static void countNumberOfFolders(File f) {
        count++;
        File[] files = f.listFiles();
        if (files != null && files.length > 0) {
            for (File file : files) {
                if (file.isDirectory()) {
                    countNumberOfFolders(file);
                }

            }
        }
    }

}

class MyThread extends Thread
{
    private String name;
    private File file;
    static CountDownLatch latch;
    static {
        latch = new CountDownLatch(ThreadDemo02.count);
    }

    public MyThread(String name, File file) {
        this.name = name;
        this.file = file;
    }

    @Override
    public void run() {
        File[] files = file.listFiles();
        if (files != null && files.length > 0) {
            for (File f : files) {
                if (f.isDirectory()) {
                    new MyThread(f.getName(), f).start();
                }
//                列印檔名不方便看最後的耗時列印,註釋掉
//                }else {
//                    System.out.println(Thread.currentThread().getName()+":"+f);
//                }
            }
        }
        System.out.println(name + " 執行結束");
        latch.countDown();
    }
}

結果如下:

http 執行結束
output 執行結束
dom 執行結束
compiler 執行結束
util 執行結束
耗時: 350 ms

可以看到確實是等到所有的執行緒執行結束後才進行的下一步。

如上 結題

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章