1. 程式人生 > >併發(十六):不要使用Thread.join()——並行變序列

併發(十六):不要使用Thread.join()——並行變序列

在多執行緒程式的編寫中,為了同步執行緒的執行狀態,我們為了方便,經常會使用Thread.join()方法,須不知此方法有重大的效能缺陷,能將多執行緒程式變成單執行緒程式,執行時間瞬間翻倍,示例如下:

/**
 *  用於長時間的任務計算,一般求fabic(40)就會花費1秒的時間
 *  花費時間呈指數增長速度
 */
static long fabic(int n) {
    if(n < 0) {
        throw new NumberFormatException("不能小於0");
    }
    if(n == 1 || n == 2) {
        return
1; } return fabic(n - 1) + fabic(n - 2); } public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis(); int NUM = 3; for(int i = 0; i < NUM; i ++) { // 建立執行緒 Thread th = new Thread(() -> { fabic(35
+ i); }); th.start(); th.join(); } // 列印花費的時間 System.out.println(System.currentTimeMillis() - startTime); }

通過監測應用程式的執行時間,或者線上程內新增除錯日誌,我們發現,現在三個執行緒會依次執行,即執行緒0執行完了才執行執行緒1,類似於連續呼叫了3次th.run()方法(其實效率更低,因為還有執行緒建立、切換、銷燬的時間)。

在上述的程式碼測試中,我們還發現了一個令人驚訝的現象,呼叫fabic的遞迴函式,JAVA的執行時間竟然超過Python 100多倍,下面是成績對比:

斐波那契數N JAVA(ms) Python(ms) 結果
35 31 2991 9227465
40 362 33533 102334155

當然用遞迴求斐波那契數的方法絕對不是一種高效的方法,但能否說明Python對遞迴優化得不夠,還是Python對棧楨的設計策略存在問題?更高效的求斐波那契數的方法如下:

#   用公式求導更快
def fabic(n):
    if(n < 1):
        raise Exception('the argument can not less than 0')
    #   輔助陣列
    result = [0, 1, 1]
    if(n < 2):
        return 1
    while(n > 2):
        result.pop(0)
        result.append(result[0] + result[1])
        n = n - 1
    else:
        return result[2]

所以,除了在除錯環境中,可以執行Thread.join()方法,否則還不如不要啟動執行緒,直接執行方法呼叫,正確的方法是應該使用CountDownLatch、CyclicBarrier這樣的執行緒工具,如下:

static class FabicTask implements Runnable {

    private int num;

    private final CyclicBarrier barrier;

    public FabicTask(int num, CyclicBarrier barrier) {
        super();
        this.num = num;
        this.barrier = barrier;
    }

    public void run() {         
        fabic(num);
        try {
            //  發訊號等待結束
            this.barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

public static void main(String[] args) throws InterruptedException {
    int NUM = 3;
    long startTime = System.currentTimeMillis();
    CyclicBarrier barrier = new CyclicBarrier(NUM, () -> {
        //  計算耗費時間
        System.out.println(System.currentTimeMillis() - startTime); 
    });
    for(int i = 0; i < NUM; i ++) {
        new Thread(new FabicTask(41, barrier)).start();
    }
}

結論

不要再生產環境中使用Thread.join()方法。