使用執行緒池過程中肯能遇到的問題
阿新 • • 發佈:2020-07-13
在上篇部落格中,我嘗試通過一個簡單的Demo介紹了一下執行緒池的構造引數和工作過程,這一篇部落格則會繼續探討一下在使用執行緒池過程中可能遇到的問題。
1.執行緒池使用時需要遵守的規範
在阿里的Java的開發手冊中對於執行緒池的使用用如下幾條規範
- 【強制】建立執行緒或執行緒池時請指定有意義的執行緒名稱,方便出錯時回溯。
- 【強制】執行緒資源必須通過執行緒池提供,不允許在應用中自行顯式建立執行緒
- 【強制】執行緒池不允許使用Executors去建立,而是通過ThreadPoolExecutor的方式,這 樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。
- 說明:Executors返回的執行緒池物件的弊端如下: 1) FixedThreadPool和SingleThreadPool: 允許的請求佇列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。 2) CachedThreadPool: 允許的建立執行緒數量為 Integer.MAX_VALUE,可能會建立大量的執行緒,從而導致 OOM。
關於規範的前兩點很好理解,接下來我們著重來看一下第三點。
- 使用newFixedThreadPool()構建執行緒池
@GetMapping("/oom1") public void oom1() throws InterruptedException { ThreadPoolExecutor threadPool = ((ThreadPoolExecutor) Executors.newFixedThreadPool(1)); printfStatus(threadPool); for (int i = 0; i < 100000000; i++) { threadPool.execute(() -> { String payload = IntStream.rangeClosed(1, 1000000) .mapToObj(__ -> "a") .collect(Collectors.joining("")) + UUID.randomUUID().toString(); try { TimeUnit.HOURS.sleep(1); } catch (InterruptedException e) { log.info(payload); } }); } threadPool.shutdown(); threadPool.awaitTermination(1, TimeUnit.HOURS); } private void printfStatus(ThreadPoolExecutor threadPool) { Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { log.info("======================"); log.info("Pool Size: {}", threadPool.getPoolSize()); log.info("Active Threads:{}", threadPool.getActiveCount()); log.info("Number of Tasks Completed:{}", threadPool.getCompletedTaskCount()); log.info("Number of Task in Queue:{}", threadPool.getQueue().size()); log.info("====================="); }, 0, 1, TimeUnit.SECONDS); }
執行結果
Exception in thread "http-nio-8080-exec-1" Exception in thread "http-nio-8080-exec-2" java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: Java heap space
很顯然在我們對Executors.newFixedThreadPool(1))建立的單執行緒的執行緒池傳入大量任務時出現了OutOfMemoryError,至於為什麼會出現這個問題我們可以看一下newFixedThreadPool的原始碼
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
在構建FixedThreadPool時,任務佇列採用的是LinkedBlockingQueue,而LinkedBlockingQueue是一個Inter.MAX_VALUE長度的佇列,由於超過核心執行緒數的任務會被儲存在任務佇列之中,因此在傳入大量任務後會導致大量任務堆積在LinkedBlockingQueue的任務佇列之中,從而導致OOM的產生。