關於線程池你不得不知道的一些設置

微信公衆號「後端進階」,專一後端技術分享:Java、Golang、WEB框架、分佈式中間件、服務治理等等。
老司機傾囊相授,帶你一路進階,來不及解釋了快上車!

看完我上一篇文章「你都理解建立線程池的參數嗎?」以後,當遇到這種問題,你以爲你徹底可以唬住面試官了,50k輕鬆到手。卻不知,要是面試官此刻給你來個反殺:java

初始化線程池時能夠預先建立線程嗎?線程池的核心線程能夠被回收嗎?爲何?git

若是此刻你一臉懵逼,這個要慌,問題很大,50k立刻變5k。github

有細心的網友早就想到了這個問題:面試

在ThreadPoolExecutor線程池中,還有一些不經常使用的設置。我建議若是您在應用場景中沒有特殊的要求,就不須要使用這些設置。後端

初始化線程池時能夠預先建立線程嗎?

prestartAllCoreThreads

初始化線程池時是能夠預先建立線程的,初始化線程池後,再調用prestartAllCoreThreads()方法,便可預先建立corePoolSize數量的核心線程,咱們看源碼:微信

public int prestartAllCoreThreads() {
    int n = 0;
    while (addWorker(null, true))
        ++n;
    return n;
}
private boolean addWorker(Runnable firstTask, boolean core) {
  // ..
}

addWorker方法目的是在線程池中添加任務並執行,若是task爲空,線程獲取任務執行時調用getTask()方法,該方法從blockingQueue阻塞隊列中阻塞獲取任務執行,所以線程不會釋放,留存在線程池中,若是core=true,說明任務只能利用核心線程來執行。框架

因此該方法會在線程池總預先建立沒有任務執行的線程,數量爲corePoolSize。分佈式

下面咱們測試一下:測試

從測試結果來看,線程池中已經預先建立了corePoolSize數量的空閒線程。spa

prestartCoreThread

prestartCoreThread()一樣能夠預先建立線程,只不過該方法只會與建立1條線程,咱們來看源碼:

public boolean prestartCoreThread() {
    return workerCountOf(ctl.get()) < corePoolSize &&
        addWorker(null, true);
}

從方法源碼可知,若是此時工做線程數量小於corePoolSize,那麼就調用addWorker建立1條空閒核心線程。

下面咱們測試一下:

從測試結果來看,線程池中已經預先建立了1條空閒線程。

線程池的核心線程能夠被回收嗎?

你可能會想到將corePoolSize的數量設置爲0,從而線程池的全部線程都是「臨時」的,只有keepAliveTime存活時間,你的思路也許時正確的,但你有沒有想過一個很嚴重的後果,corePoolSize=0時,任務須要填滿阻塞隊列纔會建立線程來執行任務,阻塞隊列有設置長度還好,若是隊列長度無限大呢,你就等着OOM異常吧,因此用這種設置行爲並非咱們所須要的。

有沒有什麼設置能夠回收核心線程呢?

allowCoreThreadTimeOut

ThreadPoolExecutor有一個私有成員變量:

private volatile boolean allowCoreThreadTimeOut;

若是allowCoreThreadTimeOut=true,核心線程在規定時間內會被回收。

上面我也說了,當線程空閒時會從blockingQueue阻塞隊列中阻塞獲取任務執行,因此咱們來看看是保證核心線程不被銷燬的,咱們直接定位到源碼部位:

java.util.concurrent.ThreadPoolExecutor#getTask:

boolean timedOut = false; // Did the last poll() time out?
for (;;) {
    // Are workers subject to culling?
    boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

    try {
        Runnable r = timed ?
            workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
        workQueue.take();
        if (r != null)
            return r;
        timedOut = true;
    } catch (InterruptedException retry) {
        timedOut = false;
    }
}

這裏的關鍵值timed,若是allowCoreThreadTimeOut=true或者此時工做線程大於corePoolSize,timed=true,若是timed=true,會調用poll()方法從阻塞隊列中獲取任務,不然調用take()方法獲取任務。

下面我來解釋這兩個方法:

  1. poll(long timeout, TimeUnit unit):從BlockingQueue取出一個任務,若是不能當即取出,則能夠等待timeout參數的時間,若是超過這個時間還不能取出任務,則返回null;
  2. take():從blocking阻塞隊列取出一個任務,若是BlockingQueue爲空,阻斷進入等待狀態直到BlockingQueue有新的任務被加入爲止。

到這裏,咱們就很好地解釋了,當allowCoreThreadTimeOut=true或者此時工做線程大於corePoolSize時,線程調用BlockingQueue的poll方法獲取任務,若超過keepAliveTime時間,則返回null,timedOut=true,則getTask會返回null,線程中的runWorker方法會退出while循環,線程接下來會被回收。

下面咱們測試一下:

能夠看到,核心線程被回收了。

公衆號「後端進階」,專一後端技術分享!

相關文章
相關標籤/搜索