1. 舉個案例,復現異常:
public class ThreadPoolExecutorTest implements Runnable {
int indexV = 0;
public ThreadPoolExecutorTest(int index) {
indexV = index;
}
public static void main(String[] args) {
// 由於ArrayBlockingQueue內部只使用了一個鎖來隔離讀和寫的操作,因此效率沒有使用了兩個鎖來隔離讀寫操作的LinkedBlockingQueue高,故而不推薦使用ArrayBlockingQueue
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(10);// 無界隊列,但是可以固定大小;
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, queue);
for (int index = 0; index < 100; index++) {
try {
poolExecutor.submit(new ThreadPoolExecutorTest(index));
} catch (RejectedExecutionException e) {
e.printStackTrace();
System.out.println("線程池關閉後不允許向線程池中添加任務");
}
}
}
@Override
public void run() {
System.out.println("厲害咯,我的哥: " + indexV);
}
}
執行以上代碼,會報RejectedExecutionException異常,見文知義是某些線程被線程池執行器拒絕執行;
java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@891d76 rejected from java.util.concurrent.ThreadPoolExecutor@121e5a[Running, pool size = 10, active threads = 7, queued tasks = 3, completed tasks = 14]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:110)
at javabasic.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:51)
2.ThreadPoolExecutor 方法的參數定義:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
參數含義:
序號 | 參數 | 含義 | 備註 |
1 | corePoolSize | 核心線程數量,線程池初始化時設定 | corePoolSize 大小和 maximumPoolSize 大小一致的話 線程池中的線程將不會空閒、 keepAliveTime 和 timeUnit 就不會再起作用 |
2 | maximumPoolSize | 線程池最大線程數(非核心線程) 核心線程 和 非核心線程 共同使用線程池、但是核心線程是不會被回收的、回收條件是線程池中的線程數量大於核心線程數 | |
3 | keepAliveTime | 如果當前線程池中線程數大於corePoolSize。多餘的線程、在等待keepAliveTime時間後如果還沒有新的線程任務指派給它、它就會被回收 | |
4 | unit | 等待時間keepAliveTime的單位 | |
5 | workQueue | 等待隊列, | 默認SynchronousQueue一個沒有存儲空間的阻塞隊列 ,將任務同步交付給工作線程; 可以使用無界隊LinkedBlockingQueue; 有界隊列ArrayBlockingQueue; 以及優先級隊列PriorityBlockingQueue |
6 | RejectedExecutionHandler | 飽和策略(分4中) | AbortPolicy(默認):直接拋棄 CallerRunsPolicy:用調用者的線程執行任務(始終此種方式,可以避免線程被線程池拒絕的情況) DiscardOldestPolicy:拋棄隊列中最久的任務 DiscardPolicy:拋棄當前任務 |
3.異常產生的原因:
當線程池裏的線程都繁忙的時候,新任務會被提交給阻塞隊列保存,當提交給阻塞隊列的任務,超出了該隊列的最大容量時。線程池就會拒絕接收新任務,隨即拋出異常;簡單的理解爲:隊列的存儲空間設置小了,但是實際業務中千萬不要指着調整隊列的大小來完全解決該問題;再往下看...
4. RejectedExecutionHandler的四種飽和策略:
序號 | 策略名稱 | 含義 | 備註 |
1 | AbortPolicy | 終止策略是默認的飽和策略,當隊列滿時,會拋出一個RejectExecutionException異常,客戶可以捕獲這個異常,根據需求編寫自己的處理代碼 | 不是解決問題的根本 |
2 | DiscardPolicy | 策略會悄悄拋棄該任務。 | 建議最好不用,水太深 |
3 | DiscardOldestPolicy | 策略將會拋棄下一個將要執行的任務,如果此策略配合優先隊列PriorityBlockingQueue,該策略將會拋棄優先級最高的任務 | |
4 | CallerRunsPolicy | 調用者運行策略,該策略不會拋出異常,不會拋棄任務,而是將任務回退給調用者線程執行(調用execute方法的線程),由於任務需要執行一段時間,所以在此期間不能提交任務,從而使工作線程有時間執行正在執行的任務。 | 非常適合我們的業務場景 |
5. 解決辦法,給定義的線程池加一個飽和策略:
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, queue, new CallerRunsPolicy());
6.爲什麼不建議使用FixedThreadPool,SingleThreadPool;CachedThreadPool,ScheduledThreadPool四種線程池:
序號 | 線程池名稱 | 規則 | 問題點 |
1 | FixedThreadPool | 允許的請求隊列長度爲 Integer.MAX_VALUE | 會堆積大量的請求,從而導致 OOM |
2 | SingleThreadPool | ||
3 | CachedThreadPool | 允許的創建線程數量爲 Integer.MAX_VALUE | 會創建大量的線程,從而導致 OOM |
4 | ScheduledThreadPool |