Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:714)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:950)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1368)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.wenniuwuren.concurrent.newCachedThreadPoolTest.main(newCachedThreadPoolTest.java:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
業(yè)務開發(fā)過程中, 比如說我們要實現(xiàn)一個功能, 但是該功能需要調(diào)用其他系統(tǒng)的多個接口做數(shù)據(jù)組裝, 多個被調(diào)用接口之間無依賴關系, 按照正常邏輯, 我們可以串行依次調(diào)用多個接口, 假如有三個接口, 我們的代碼如下:
//偽代碼 偽代碼 偽代碼! 重要的事情說三遍
public Result demo(){
//調(diào)用第一個接口, 耗時1s
Result interface1Result = interface1();
//調(diào)用第二個接口, 耗時2s
Result interface2Result = interface2();
//調(diào)用第三個接口, 耗時3s
Result interface3Result = interface3();
//組裝數(shù)據(jù)
Result result = interface1Result + interface2Result +interface3Result;
return result;
}
滿足業(yè)務需要,沒有問題, 當我們考慮性能的時候, 這個接口的最小耗時等于三個接口的耗時之和, 也就是6s, 針對這種情況, 我們想當然的覺得, 可以把串行的接口調(diào)用改成并行, 這樣的話, 接口響應時間應該提升為3s, 偽代碼入下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//偽代碼 偽代碼 偽代碼! 重要的事情說三遍
public Result demo(){
//調(diào)用第一個接口, 耗時1s
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
Result interface1Result = interface1();
}
});
}
//調(diào)用第二個接口, 耗時2s
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
Result interface2Result = interface2();
}
});
}
//調(diào)用第三個接口, 耗時3s
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
Result interface3Result = interface3();
}
});
}
//組裝數(shù)據(jù)
Result result = interface1Result + interface2Result +interface3Result;
return result;
}
這種情況下, 我們使用線程池來并行調(diào)用三個接口, 最終響應時間得到了提升, 但是單機的tps在壓測的時候, 就會出現(xiàn)我們文章標題的錯誤, 因為每個方法需要3s的處理時長, 當壓力不斷增加的時候, 線程池沒有可用的線程時, 會一直新建線程, 當超出jvm的棧內(nèi)存大小時, 就會報出無法再創(chuàng)建線程的錯誤.
這種情況下, 我們可以使用定長的線程池, 比如估計一個線程池大小, 100, 當無線程可用的時候, 我們就阻塞等待, 這種情況比較穩(wěn), 但需要能夠比較準確的估算線程池大小, 要不然服務器資源會有一定的浪費, 這不符合我們勤儉持家程序員的風格, 于是下面我們介紹一下, 如何準確的估算線程池的大小:
引自:《Java Concurrency in Practice》即《java并發(fā)編程實踐》
如上圖,在《Java Concurrency in Practice》一書中,給出了估算線程池大小的公式:
Nthreads=Ncpu*Ucpu*(1+w/c),其中
Ncpu=CPU核心數(shù)
Ucpu=cpu使用率,0~1
W/C=等待時間與計算時間的比率
比如說, 服務器cpu為32核, 一般cpu使用到80%會引起系統(tǒng)告警, 等待時間估計為 0.2s, 計算時間為 0.1s,
針對這種情況:
Nthreads=32*0.8*(1+0.2/0.1) = 76.8
所以我們就設置線程池大小為 75 就ok了