併發(十六):不要使用Thread.join()——並行變序列
阿新 • • 發佈:2019-02-01
在多執行緒程式的編寫中,為了同步執行緒的執行狀態,我們為了方便,經常會使用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()方法。