CompletableFuture默認線程池-生產問題


在jdk7中,我們使用線程池可能會使用ExecutorService,默認有四種方式

Executors.newSingleeThreadPool()

Executors.newFixedThreadPool()

Executors.newCacheThreadPool()

Executors.newScheduledThreadPool()

在jdk8中,CompletableFuture騰空出世,它簡化了異步任務的寫法,提供了很多異步任務的計算方式。

言歸正傳,現在生產上面出現的問題是,在流量激增的時候,響應很慢,慢慢的所有請求都無妨得到響應結果。

解決方案:

①查看cpu和內存使用率

cpu使用率很低,5%左右,內存使用一直不變,基本排除不是他們的問題。

①.查看gc

看到full gc沒有發生,young gc 雖然增加了一點,但是平均響應時間也就是50ms,也算正常了。

② 分析dump堆

下了一個126M的dump包,有一個類占了60m。首先懷疑是不是內存溢出了,但是通過分析,這部分緩存是必需要做的,並且當時dump下來的包確實有點小,數據觀測的不夠准確。

③.查看棧信息

發現有很多ForkJoinPool.commonPool-worker-線程正在等待,其實使用過CompletableFuture的同學就知道,它里面用的是ForkJoin池來實現的。有想了解線程池源碼的可以去讀讀這篇文章。

為什么這里會有這么多的線程在等待呢?生產上面的服務器使用的是一個兩核的服務器,線程池里面只會是1個線程可以執行。為什么是一個,請看源碼。

@Test
public void test12() throws InterruptedException { 先做一個單元測試
CompletableFuture.runAsync(()->{ //在此處打斷點
System.out.println("111");
});
Thread.sleep(400000);
}
一步一步把代碼貼出來,看官看好。

public static CompletableFuture<Void> runAsync(Runnable runnable) { //運行線程的方法
return asyncRunStage(asyncPool, runnable);
}
asyncPool是什么?看一下這個值的設置。

private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
useCommonPool是什么?

private static final boolean useCommonPool =
(ForkJoinPool.getCommonPoolParallelism() > 1);
public static int getCommonPoolParallelism() {
return commonParallelism;
}
commonParallelism就是那個並發的線程數,它又是怎么來的呢?

static {
// initialize field offsets for CAS etc
。。。。。。
commonMaxSpares = DEFAULT_COMMON_MAX_SPARES;
defaultForkJoinWorkerThreadFactory =
new DefaultForkJoinWorkerThreadFactory();
modifyThreadPermission = new RuntimePermission("modifyThread");

common = java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction<ForkJoinPool>() {
public ForkJoinPool run() { return makeCommonPool(); }}); //重點看makeCommonPool方法
int par = common.config & SMASK; // 獲取到par SMASK的值是 65535 也就是1111111111111111 &操作還是common.config本身,看樣子還是要看看config是怎么來的
commonParallelism = par > 0 ? par : 1; 想知道par是什么值,這個值為負數默認是1
}
private static ForkJoinPool makeCommonPool() {
int parallelism = -1; //這個並發的線程數默認是-1
ForkJoinWorkerThreadFactory factory = null;
。。。。。。
if (parallelism < 0 &&
(parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0) //看到了吧,線程池中的處理線程數=電腦核數-1
parallelism = 1;
if (parallelism > MAX_CAP)
parallelism = MAX_CAP;
return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE,
"ForkJoinPool.commonPool-worker-"); //指定線程的名字
}
到此分析完畢,使用了逆推法。

由於生產服務器電腦核數較小,而在CompletableFuture代碼中又使用了默認的線程池,處理的線程個數是電腦核數-1。這樣等有大請求量過來,處理邏輯又很復雜,很多線程都在等待執行,慢慢拖垮了服務器。

調整線程池的大小
《Java並發編程實戰》(http://mng.bz/979c)一書中,Brian Goetz和合著者們為線程池大小
的優化提供了不少中肯的建議。這非常重要,如果線程池中線程的數量過多,最終它們會競爭
稀缺的處理器和內存資源,浪費大量的時間在上下文切換上。反之,如果線程的數目過少,正
如你的應用所面臨的情況,處理器的一些核可能就無法充分利用。Brian Goetz建議,線程池大
小與處理器的利用率之比可以使用下面的公式進行估算:
N threads = N CPU * U CPU * (1 + W/C)
其中:
❑N CPU 是處理器的核的數目,可以通過 Runtime.getRuntime().availableProce-
ssors() 得到
❑U CPU 是期望的CPU利用率(該值應該介於0和1之間)
❑W/C是等待時間與計算時間的比率

這里太啰嗦了,一般的設置線程池的大小規則是

如果服務是cpu密集型的,設置為電腦的核數

如果服務是io密集型的,設置為電腦的核數*2
---------------------
作者:歸海一刀之漁舟唱晚
來源:CSDN
原文:https://blog.csdn.net/u011381576/article/details/80013146
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM