天天看點

面試官:有多少種線程池、拒絕政策、阻塞隊列?

線程池的工作原理

ThreadPoolExecutor(int corePoolSize,// 核心線程數
                   int maximumPoolSize,//最大線程數
                   long keepAliveTime,//空閑線程存活時間
                   TimeUnit unit,//存活時間機關
                   BlockingQueue<Runnable> workQueue,//阻塞隊列
                   RejectedExecutionHandler handler)//拒絕政策      

當ThreadPoolExecutor線程池被建立的時候,裡邊是沒有工作線程的,直到有任務進來(執行了execute方法)才開始建立線程去工作,工作原理如下(即execute方法運作原理):

調用線程池的execute方法的時候如果目前的工作線程數 小于 核心線程數,則建立新的線程執行任務;否則将任務加入阻塞隊列。如果隊列滿了則根據最大線程數去建立額外(核心線程數以外)的工作線程去執行任務;如果工作線程數達到了最大線程數,則根據拒絕政策去執行。存活時間到期的話隻是回收核心線程(maximumPoolSize - corePoolSize)以外的線程

// 分3個步驟進行:
// 1. 如果運作的線程少于corePoolSize,請嘗試使用給定的指令作為第一個線程啟動一個新線程的任務。對addWorker的調用會自動檢查runState和workerCount,這樣可以防止虛假警報的增加當它不應該的時候,傳回false。
// 2. 如果任務可以成功排隊,那麼我們仍然需要來再次檢查我們是否應該添加線程(因為自從上次檢查後,現有的已經死了)或者那樣自進入此方法後池就關閉了。是以我們重新檢查狀态,并在必要時復原隊列停止,或啟動一個新線程(如果沒有線程)。
// 3.如果我們不能将任務放入隊列,那麼我們嘗試添加一個新的線程。如果它失敗了,我們知道我們被關閉或飽和了是以拒絕這個任務。
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // 第一步
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 第二步驟
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 第三步
        else if (!addWorker(command, false))
            reject(command);
    }      

五種線程池

ExecutorService threadPool = null;
threadPool = Executors.newCachedThreadPool();
//有緩沖的線程池,線程數 JVM 控制
threadPool = Executors.newFixedThreadPool(3);
//固定大小的線程池
threadPool = Executors.newScheduledThreadPool(2);
threadPool = Executors.newSingleThreadExecutor();
//單線程的線程池,隻有一個線程在工作
threadPool = new ThreadPoolExecutor();
//預設線程池,可控制參數比較多        

ThreadPoolExecutor參數:

public class ThreadPoolExecutor extends AbstractExecutorService {
    .....
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}      

參數含義以及功能:

corePoolSize:核心池的大小,這個參數跟後面講述的線程池的實作原理有非常大的關系。在建立了線程池後,預設情況下,線程池中并沒有任何線程,而是等待有任務到來才建立線程去執行任務,除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個方法的名字就可以看出,是預建立線程的意思,即在沒有任務到來之前就建立corePoolSize個線程或者一個線程。預設情況下,在建立了線程池後,線程池中的線程數為0,當有任務來之後,就會建立一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中;

maximumPoolSize:線程池最大線程數,這個參數也是一個非常重要的參數,它表示線上程池中最多能建立多少個線程;

keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。預設情況下,隻有當線程池中的線程數大于corePoolSize時,keepAliveTime才會起作用,直到線程池中的線程數不大于corePoolSize,即當線程池中的線程數大于corePoolSize時,如果一個線程空閑的時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。但是如果調用了allowCoreThreadTimeOut(boolean)方法,線上程池中的線程數不大于corePoolSize時,keepAliveTime參數也會起作用,直到線程池中的線程數為0;

unit:參數keepAliveTime的時間機關,有7種取值,在TimeUnit類中有7種靜态屬性:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小時
TimeUnit.MINUTES;           //分鐘
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //納秒      

workQueue

:一個阻塞隊列,用來存儲等待執行的任務,這個參數的選擇也很重要,會對線程池的運作過程産生重大影響,一般來說,這裡的阻塞隊列有以下幾種選擇:

ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;      

ArrayBlockingQueue和PriorityBlockingQueue使用較少,一般使用LinkedBlockingQueue和Synchronous。線程池的排隊政策與BlockingQueue有關。

threadFactory:線程工廠,主要用來建立線程;

handler:表示當拒絕處理任務時的政策,有以下四種取值:

ThreadPoolExecutor.AbortPolicy:(預設)丢棄任務并抛出RejectedExecutionException異常。

ThreadPoolExecutor.DiscardPolicy:也是丢棄任務,但是不抛出異常。

ThreadPoolExecutor.DiscardOldestPolicy:丢棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)

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

四種拒絕政策

RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();
//預設,隊列滿了丢任務抛出異常
rejected = new ThreadPoolExecutor.DiscardPolicy();
//隊列滿了丢任務不抛異常
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();
//将最早進入隊列的任務删,之後再嘗試加入隊列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();
//如果添加到線程池失敗,那麼主線程會自己去執行該任務;如果執行程式已關閉(主線程運作結束),則會丢棄該任務      

當然也可以根據應用場景實作 RejectedExecutionHandler 接口,自定義飽和政策,如記錄 日志或持久化存儲不能處理的任務

三種阻塞隊列

BlockingQueue<Runnable> workQueue = null;
workQueue = new ArrayBlockingQueue<>(5);
//基于數組的先進先出隊列,有界
workQueue = new LinkedBlockingQueue<>();
//基于連結清單的先進先出隊列,無界
workQueue = new SynchronousQueue<>();
//無緩沖的等待隊列,無界