Java 8並行流的效能陷阱

banq發表於2019-08-15

並行化流被分成多個塊,每個塊獨立處理,結果在最後彙總。
CPU密集型程式碼如下:

private long countPrimes(int max) {
    return range(1, max).parallel().filter(this::isPrime).count();
}
private boolean isPrime(long n) {
    return n > 1 && rangeClosed(2, (long) sqrt(n)).noneMatch(divisor -> n % divisor == 0);
}

 countPrimes 計算1到最大值之間的素數的數量。數字流由range方法建立,切換到並行模式,過濾掉非素數,剩餘的計算總數。由於isPrime 方法極其無效且佔用大量CPU,我們可以利用並行化並利用所有可用的CPU核心。

我們來看另一個例子:

private List<StockInfo> getStockInfo(Stream<String> symbols) {
     return symbols.parallel()
            .map(this::getStockInfo) //slow network operation
            .collect(toList());
}

輸入是一個股票程式碼列表,我們必須呼叫慢速網路操作來獲取有關股票的一些細節。在這裡,我們不處理CPU密集型操作,但我們也可以利用並行化。並行執行多個網路請求是個好主意。同樣,並行流的一個很好的任務,你同意嗎?

如果您這樣做,請再次檢視上一個示例。有一個很大的錯誤。你看到了嗎?問題是所有並行流都使用公共fork-join執行緒池。如果提交長時間執行的任務,則會有效地阻塞池中的所有執行緒。因此,您將阻塞使用並行流的所有其他任務。

想象一下servlet環境,當一個請求呼叫時getStockInfo() ,另一個請求呼叫  countPrimes()。即使每個都需要不同的資源,也會阻止另一個。更糟糕的是,你不能為並行流指定執行緒池; 整個類載入器必須使用相同的。

讓我們在下面的例子中說明它:

private void run() throws InterruptedException {
 ExecutorService es = Executors.newCachedThreadPool();
 // Simulating multiple threads in the system
 // if one of them is executing a long-running task.
 // Some of the other threads/tasks are waiting
 // for it to finish
 es.execute(() -> countPrimes(MAX, 1000));
 //incorrect task
 es.execute(() -> countPrimes(MAX, 0));
 es.execute(() -> countPrimes(MAX, 0));
 es.execute(() -> countPrimes(MAX, 0));
 es.execute(() -> countPrimes(MAX, 0));
 es.execute(() -> countPrimes(MAX, 0));
 es.shutdown();
 es.awaitTermination(60, TimeUnit.SECONDS);
}
private void countPrimes(int max, int delay) {
  System.out.println( range(1, max).parallel() .filter(this::isPrime).peek(i -> sleep(delay)).count() );

}


在這裡,我們模擬系統中的六個執行緒。所有這些都在執行CPU密集型任務,第一個被“暫停”,在它找到素數後就睡了一秒鐘。這只是一個人為的例子; 你可以想象一個被卡住或執行阻塞操作的執行緒。

問題是:執行此程式碼時會發生什麼?我們有六個任務; 其中一個將需要一整天才能完成,其餘的應該更快完成。毫不奇怪,每次執行程式碼時,都會得到不同的結果。你想在生產系統中有這樣的行為嗎?一個杜塞的任務取消了應用程式的其餘部分?我猜不會。

關於如何確保永遠不會發生這樣的事情,只有兩種選擇。第一個是確保提交到公共fork-join池的所有任務都不會卡,必須在合理的時間內完成。但這說起來容易做起來難,尤其是在複雜的應用程式中。

另一種選擇是不使用並行流,並等到Oracle允許我們指定用於並行流的執行緒池。



 

相關文章