面試官:說說你知道多少種線程池拒絕策略

前言

線程池,相信不少人都有用過,沒用過相信的也有學習過。可是,線程池的拒絕策略,相信知道的人會少量多。java

四種線程池拒絕策略

當線程池的任務緩存隊列已滿而且線程池中的線程數目達到maximumPoolSize時,若是還有任務到來就會採起任務拒絕策略,一般有如下四種策略:spring

ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:丟棄任務,可是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新提交被拒絕的任務
ThreadPoolExecutor.CallerRunsPolicy:由調用線程(提交任務的線程)處理該任務
複製代碼

線程池默認的拒絕策略

既然有四種拒絕策略能夠選擇,那麼線程池的默認拒絕策略是什麼呢?查看java.util.concurrent.ThreadPoolExecutor類的源碼,咱們能夠看到:緩存

/** * The default rejected execution handler */
private static final RejectedExecutionHandler defaultHandler =
    new AbortPolicy();
複製代碼

線程池的默認拒絕策略爲AbortPolicy,即丟棄任務並拋出RejectedExecutionException異常。咱們能夠經過代碼來驗證這一點,現有以下代碼:bash

public class ThreadPoolTest {

    public static void main(String[] args) {

        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
        ThreadFactory factory = r -> new Thread(r, "test-thread-pool");
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
                0L, TimeUnit.SECONDS, queue, factory);
        while (true) {
            executor.submit(() -> {
                try {
                    System.out.println(queue.size());
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }

}
複製代碼

這裏是一個默認的線程池,沒有設置拒絕策略,設置了最大線程隊列是100。運行代碼:併發

結果是符合預期的,這也證實了線程池的默認拒絕策略是ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。less

設置線程池拒絕策略

若是咱們想要根據實際業務場景須要,設置其餘的線程池拒絕策略,能夠經過ThreadPoolExecutor重載的構造方法進行設置:ide

如今的開發中,相信你們都有使用spring,其實咱們也能夠經過spring提供的org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor構建線程池。以下:學習

@Configuration
public class TaskExecutorConfig implements AsyncConfigurer {
    /** * Set the ThreadPoolExecutor's core pool size. */
    private static final int CORE_POOL_SIZE = 5;
    /** * Set the ThreadPoolExecutor's maximum pool size. */
    private static final int MAX_POOL_SIZE = 5;
    /** * Set the capacity for the ThreadPoolExecutor's BlockingQueue. */
    private static final int QUEUE_CAPACITY = 1000;

    /** * 經過重寫getAsyncExecutor方法,制定默認的任務執行由該方法產生 * <p> * 配置類實現AsyncConfigurer接口並重寫getAsyncExcutor方法,並返回一個ThreadPoolTaskExevutor * 這樣咱們就得到了一個基於線程池的TaskExecutor */
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setQueueCapacity(QUEUE_CAPACITY);
        taskExecutor.initialize();
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        return taskExecutor;
    }
}
複製代碼

經過ThreadPoolTaskExecutor的setRejectedExecutionHandler設置拒絕策略便可。網站

拒絕策略場景分析

AbortPolicy

ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。ui

A handler for rejected tasks that throws a {@code RejectedExecutionException}.
複製代碼

這是線程池默認的拒絕策略,在任務不能再提交的時候,拋出異常,及時反饋程序運行狀態。若是是比較關鍵的業務,推薦使用此拒絕策略,這樣子在系統不能承載更大的併發量的時候,可以及時的經過異常發現。

DiscardPolicy

ThreadPoolExecutor.DiscardPolicy:丟棄任務,可是不拋出異常。若是線程隊列已滿,則後續提交的任務都會被丟棄,且是靜默丟棄。

A handler for rejected tasks that silently discards therejected task.
複製代碼

使用此策略,可能會使咱們沒法發現系統的異常狀態。建議是一些可有可無的業務採用此策略。例如,本人的博客網站統計閱讀量就是採用的這種拒絕策略。

DiscardOldestPolicy

ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新提交被拒絕的任務。

A handler for rejected tasks that discards the oldest unhandled request and then retries {@code execute}, unless the executor is shut down, in which case the task is discarded.
複製代碼

此拒絕策略,是一種喜新厭舊的拒絕策略。是否要採用此種拒絕策略,還得根據實際業務是否容許丟棄老任務來認真衡量。

CallerRunsPolicy

ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.
複製代碼

若是任務被拒絕了,則由調用線程(提交任務的線程)直接執行此任務,咱們能夠經過代碼來驗證這一點:

把以前的代碼修改以下:

public static void main(String[] args) {

    BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);
    ThreadFactory factory = r -> new Thread(r, "test-thread-pool");
    ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
                                                         0L, TimeUnit.SECONDS, queue, factory, new ThreadPoolExecutor.CallerRunsPolicy());
    for (int i = 0; i < 1000; i++) {
        executor.submit(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + ":執行任務");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
}

複製代碼

把隊列最大值改成10,打印輸出線程的名稱。執行結果以下:

經過結果能夠看到,主線程main也執行了任務,這正說明了此拒絕策略由調用線程(提交任務的線程)直接執行被丟棄的任務的。

總結

本文介紹和演示了四種線程池拒絕策略,具體使用哪一種策略,還得根據實際業務場景才能作出抉擇。

相關文章
相關標籤/搜索