ThreadPoolExecutor 合理使用自帶策略,避免線程被丟棄

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 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章