在jdk中為我們提供了三種創建線程池的方式,但是在阿里的編碼規范里面都是明確禁止使用這三種api去創建線程池,推薦我們去自定義線程池。為什么?
要回答為什么,我們需要明白創建線程池時,各參數的作用:
首先我們來看一下jdk提供的創建線程池的三個api:
1. newFixedThreadPool 創建固定數量線程的線程池。
2. newSingleThreadExecutor 創建單線程的線程池
3. newCachedThreadPool 創建一個帶有緩存的線程池
發現這幾種創建線程池的api,實質上都是依賴於ThreadPoolExecutor類來創建線程池。
那我們來看一下ThreadPoolExecutor 創建線程池時需要的參數,以及其作用。
創建一個線程池,需要7個參數。
1. corePoolSize: 線程池的核心線程數量。初始是不創建線程的。當有任務提交到線程池時,判定如果已經創建的線程數量小於核心數量,且沒有空閑線程時,則會新建一個線程去執行新提交的任務。如果已經達到核心線程數量, 則會加入到阻塞隊列中。
2.maximumPoolSize: 線程池的最大容量。當線程池的阻塞隊列放滿了, 並且線程數量還未達到線程池的最大線程數量, 則會創建新的線程,直到達到最大值
3.keepAliveTime 當阻塞隊列里面的任務被執行完了, 且有空閑線程時,指定大於核心線程池數量的部分空閑線程的存活時間, 畢竟線程也是需要消耗資源的,及時回收很有必要。當線程空閑的時間超過這個時間后,會回收掉一部分空閑線程,使其線程池中的線程數量不大於核心線程的數量
4.unit 和keepAliveTIme 配套使用,上面指定了時間的數值,但是沒有指定時間的單位(時,分,秒等), 這里需要指定時間的單位
5.workQueue 阻塞隊列,當沒有空閑線程時,多余的任務緩存的地方。
6.threadFactory 線程工廠,用來創建線程時,設定線程的一些參數。通常我們為了后續查看日志方便,可以通過這個來指定我們自定義的線程池的線程名稱
7.handler 當線程數量達到最大值時,且阻塞隊列慢了, 后續在提交任務時,沒有地方可以接受繼續的提交的任務。這種情況下的一個拒絕策略。
拒絕策略jdK,提供了四種:
// 由提交任務的線程執行任務
public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }
// 不在接收新的任務,直接拋出異常 public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } }
// 不接收也不拋出異常,空實現,忽略該任務 public static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } } // 拋棄最老的任務,從阻塞隊列中移除最早提交的任務,然后將該任務加入。 public static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } }
講解完了創建線池時,各參數的作用,那么我們現在再反過來看為什么不讓使用jdk提供的apI來創建線程池,而是需要我們自定義線程池。
newFixedThreadPool ,newSingleThreadExecutor 這兩種api 使用的阻塞隊列都是無界隊列,也就是無論有多少個任務來,我們都接收。我們的內存是有限的,阻塞隊列里面存儲的任務是越多,也就意味着占用的內存越多,這樣會導致占用大量的內存,容易引起OOM
newCachedThreadPool 而這個api 的阻塞隊列容量為0,最大線程數量為Integer 的最大值。每當有一個任務提交時,阻塞隊列存儲不了,就會新開啟一個線程,當任務比較多,則會創建大量的線程, 引起OOM.
這就是為什么我們在使用線程池時一定要自定義線程池的原因了。