聊聊線程池,這一頓操作說懵阿里面試官...

想要進階自己的開發(fā)水平,JDK源碼中一些優(yōu)秀的設計必須要經(jīng)常學習,哪怕不學習,應對面試的時候,還是要能夠應對幾招,代表自己對這些東西還是有所了解。

而線程池的源碼,這塊更是面試中經(jīng)常被問到的東西,先試著列幾個問題,看看自己對線程池的掌握程度:

  1. 創(chuàng)建線程池的參數(shù)有哪些,分別代表什么意思?

  2. 為什么阿里要求不能直接使用Executors工具類創(chuàng)建線程池?

  3. 線程池線程的數(shù)量如何配置?

  4. 一般線程池提交任務,執(zhí)行任務的過程?

  5. 線程池中ctl屬性的作用是什么?

  6. 線程池的狀態(tài)有哪些?在什么時候下會出現(xiàn)?

  7. 一般線程池中有哪些未實現(xiàn)的空方法,可以用做線程池的擴展?

  8. 線程池中每一個具體的worker線程什么時候開始執(zhí)行?執(zhí)行的過程是什么?

  9. 核心線程與非核心線程在線程池中是怎么區(qū)分的?

  10. 線程池中的那個方法可以提前創(chuàng)建核心線程?

  11. 什么情況下worker線程會退出?

  12. 核心線程會不會退出?

  13. 由于程序異常導致的退出和線程池內部機制導致的退出有什么區(qū)別?

  14. 線程池shutdown與shutdownNow有什么區(qū)別?

image.png

對上面問題都已經(jīng)了如指掌的大佬,聯(lián)系我,讓我表達對你的膜拜... 以上問題相對來說并不是很難,只要有認真看線程池源碼,都可以找到答案。然后以后有人再問你線程池相關問題時,就可以拿出來說自己對線程池的理解,來聊聊把...

郵箱:aihe.ah@alibaba-inc.com
微信:aihehe5211

常見問題

使用線程池有哪些好處?

首先在開發(fā)的過程中,為什么需要線程池呢?給我們帶來了那些好處

  • 提高系統(tǒng)的響應速度
  • 如果每次多線程操作都創(chuàng)建一個線程,會浪費時間和消耗系統(tǒng)資源,而線程池可以減少這些操作
  • 可以對多個線程進行統(tǒng)一管理,統(tǒng)一調度,提高線程池的可管理性

創(chuàng)建線程池的參數(shù)有哪些?

線程池是怎么創(chuàng)建的呢?一個是使用Executors,另外就是手動創(chuàng)建線程池,要了解其每個參數(shù)的含義。Executors創(chuàng)建線程池的話,要不就是對線程的數(shù)量沒有控制,如CachedThreadPool,要不就是是無界隊列,如FixedThreadPool。對線程池數(shù)量和隊列大小沒有限制的話,容易導致OOM異常。所以我們要自己手動創(chuàng)建線程池:

  • corePoolSize:核心線程數(shù)量,默認情況下每提交一個任務就會創(chuàng)建一個核心線程,直到核心線程的數(shù)量等于corePoolSize就不再創(chuàng)建。線程池提供了兩個方法可以提前創(chuàng)建核心線程,prestartAllCoreThreads()提前創(chuàng)建所有的核心線程,prestartCoreThread,提前創(chuàng)建一個核心線程
  • maximumPoolSize:線程池允許創(chuàng)建的最大線程數(shù)。只有當線程池隊列滿的時候才會創(chuàng)建
  • keepAliveTime:線程池空閑狀態(tài)可以等待的時間,默認對非核心線程生效,但是設置allowCoreThreadTimeOut的話對核心線程也生效
  • unit: ?;顣r間的單位,創(chuàng)建線程池的時候,keepAliveTime = unit.toNanos(keepAliveTime)
  • workQueue: 任務隊列,用于保持或等待執(zhí)行的任務阻塞隊列。BlockingQueue的實現(xiàn)類即可,有無界隊列和有界隊列
    • ArrayBlockingQueue: 基于數(shù)組結構的有界隊列,此隊列按FIFO原則對元素進行排序
    • LinkedBlockingQueue: 基于鏈表的阻塞隊列,F(xiàn)IFO原則,吞吐量通常高于ArrayBlockingQueue.
    • SynchronousQueue: 不存儲元素的阻塞隊列。每個插入必須要等到另一個線程調用移除操作。
    • PriorityBlockingQueue: 具有優(yōu)先級的無阻塞隊列
  • threadFactory: 用于設置創(chuàng)建線程的工廠。
  • handler:拒絕策略,當隊列線程池都滿了,必須采用一種策略來處理還要提交的任務。在實際應用中,我們可以將信息記錄到日志,來分析系統(tǒng)的負載和任務丟失情況JDK中提供了4中策略:
    • AbortPolicy: 直接拋出異常
    • CallerRunsPolicy: 只用調用者所在的線程來運行任務
    • DiscardOldestPolicy: 丟棄隊列中最老的一個人任務,并執(zhí)行當前任務。
    • DiscardPolicy: 直接丟棄新進來的任務

線程池提交任務的過程?

可以使用兩個方法執(zhí)行任務:

  • execute() 提交不需要返回值的任務,無法判斷是否執(zhí)行成功,具體步驟上面我們有分析
  • submit() 提交有返回值的任務,該方法返回一個future的對象,通過future對象可以判斷任務是否執(zhí)行成功。future的get方法會阻塞當前線程直到任務完成。
    • submit內部使用RunnableFuture對任務進行封裝

整體分為三個步驟:

  1. 判斷當前線程數(shù)是否小于corePoolSize,如果小于,則新建核心線程,不管核心線程是否處于空閑狀態(tài)
  2. 核心線程創(chuàng)建滿之后,后續(xù)的任務添加到workQueue中
  3. 如果workQueue滿了,則開始創(chuàng)建非核心線程直到線程的總數(shù)為maximumPoolSize
  4. 當非核心線程數(shù)也滿了,隊列也滿了的時候,執(zhí)行拒絕策略

中間會有一些對當前線程池的檢查操作。

image-20200212084315747.png

線程池數(shù)量如何配置?

  • 任務性質:CPU密集,IO密集,和混合密集
  • 任務執(zhí)行時間:長,中,低
  • 任務優(yōu)先級:高,中,低
  • 任務的依賴性:是否依賴其它資源,如數(shù)據(jù)庫連接

在代碼中可以通過:Runtime.getRuntime().availableProcessors();獲取CPU數(shù)量。線程數(shù)計算公式:

N = CPU數(shù)量
U = 目標CPU使用率,  0 <= U <= 1
W/C = 等待(wait)時間與計算(compute)時間的比率

線程池數(shù)量 =  N * U * (1 + W/C)

不過最簡單的線程數(shù)指定方式,不需要公式的話:

  • CPU密集型,創(chuàng)建線程數(shù)為CPU核數(shù) + 1
  • IO密集型,線程數(shù)最好為CPU核數(shù) * n,耗時越久,分配線程數(shù)多一些

線程池的狀態(tài)有哪些?

線程池的狀態(tài)主要通過ctl屬性來控制,通過ctl可以計算出:

  • 當前線程池狀態(tài)
  • 當前線程的數(shù)量

計算規(guī)則主要是利用了按位操作:

11100000000000000000000000000000   RUNNING
00000000000000000000000000000000   SHUTDOWN
00100000000000000000000000000000   STOP
01000000000000000000000000000000   TYDYING
01100000000000000000000000000000   TERMINATED


11100000000000000000000000000000   ctl初始值
11100000000000000000000000000000  ~CAPACITY  
private static int runStateOf(int c)     { return c & ~CAPACITY; }

11100000000000000000000000000000   ctl初始值
00011111111111111111111111111111  CAPACITY
private static int workerCountOf(int c)  { return c & CAPACITY; }
    
private static int ctlOf(int rs, int wc) { return rs | wc; }  
  • RUNNING:運行狀態(tài),接受新任務,持續(xù)處理任務隊列里的任務。
  • SHUTDOWN:調用shutdown()方法會進入此狀態(tài),不再接受新任務,但要處理任務隊列里的任務
  • STOP:調用shutdownNow()方法,不再接受新任務,不再處理任務隊列里的任務,中斷正在進行中的任務
  • TIDYING:表示線程池正在停止運作,中止所有任務,銷毀所有工作線程。
  • TERMINATED:表示線程池已停止運作,所有工作線程已被銷毀,所有任務已被清空或執(zhí)行完畢
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

關于TIDYING和TERMINATED主要有一塊代碼區(qū),可以看出來TIDYING狀態(tài)緊接著就是TERMINATED。

                        if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                            // 默認是空方法
                        terminated();
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));
                        termination.signalAll();
                    }
                    return;
                }

線程池提供的擴展方法有哪些?

默認有三個擴展方法,可以用來做一些線程池運行狀態(tài)統(tǒng)計,監(jiān)控:

 protected void beforeExecute(Thread t, Runnable r) { }  // task.run方法之前執(zhí)行
 protected void afterExecute(Runnable r, Throwable t) { }  // task執(zhí)行完之后,不管有沒有異常都會執(zhí)行
 protected void terminated() { }  

默認線程池也提供了幾個相關的可監(jiān)控屬性:

  • taskCount: 線程池需要執(zhí)行的任務數(shù)量
  • completedTaskCount: 已經(jīng)完成的任務數(shù)量
  • largestPoolSize: 線程池中曾經(jīng)創(chuàng)建的最大的線程數(shù)量
  • getPoolSize: 線程池的線程數(shù)量
  • getActiveCount: 活動的線程數(shù)

線程池中的Worker線程執(zhí)行的過程?

Worker類實現(xiàn)了Runnable方法,在成功創(chuàng)建Worker線程后就會調用其start方法。

w = new Worker(firstTask);
final Thread t = w.thread;   //理解為 w.thread = new Thread(w)
if (workerAdded) {
    t.start();
    workerStarted = true;
}


Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

Worker線程運行時執(zhí)行runWorker方法,里面主要事情:

  • 如果構造Worker的時候,指定了firstTask,那么首先執(zhí)行firstTask,否則從隊列中獲取任務
  • Worker線程會循環(huán)的getTask(),然后去執(zhí)行任務
  • 如果getTask()為空,那么worker線程就會退出
  • 在任務執(zhí)行前后,可以自定義擴展beforeExecute與afterExecute方法
  • 如果檢測到線程池為STOP狀態(tài),并且線程還沒有被中斷過的話,進行中斷處理

簡單來說就是不斷的從任務隊列中取任務,如果取不到,那么就退出當前的線程,取到任務就執(zhí)行任務。

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        // 代表著Worker是否因為用戶的程序有問題導致的死亡
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (Exception x) {
                                                  //... 不同的異常處理
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

線程池如何區(qū)分核心線程與非核心線程?

實際上內部在創(chuàng)建線程時,并沒有給線程做標記,因此無法區(qū)分核心線程與非核心線程。可以看出addWorker()方法

但是為什么可以保持核心線程一直不被銷毀呢?

其內部主要根據(jù)當前線程的數(shù)量來處理。也可以理解為,只要當前的worker線程數(shù)小于配置的corePoolSize,那么這些線程都是核心線程。線程池根據(jù)當前線程池的數(shù)量來判斷要不要退出線程,而不是根據(jù)是否核心線程

核心線程能否被退出?

上面一個問題我們說到了內部其實不區(qū)分核心線程與非核心線程的,只是根據(jù)數(shù)量來判斷是否退出線程,但是線程是如何退出的,又是如何一直處于保活狀態(tài)呢?

如果配置了allowCoreThreadTimeOut,代表核心線程在配置的keepAliveTime時間內沒獲取到任務,會執(zhí)行退出操作。也就是盡管當前線程數(shù)量小于corePoolSize也會執(zhí)行退出線程操作。

workQueue.take()方法會一直阻塞當前的隊列直到有任務的出現(xiàn),因此如果執(zhí)行的是take方法,那么當前的線程就不會退出。想要退出當前的線程,有幾個條件:

  • 1 當前的worker數(shù)量大于maximumPoolSize的worker數(shù)量。
  • 2 線程池當前處于STOP狀態(tài),即shutdownNow
  • 3 線程池處于SHUTDOWN狀態(tài),并且當前的隊列為空
  • 4 worker線程等待task超時了,并且當前的worker線程配置為可以被退出。timed=true
    • allowCoreThreadTimeOut配置為true
    • 線程數(shù)量大于核心線程數(shù)
image-20200212092008155.png

如何提前創(chuàng)建核心線程數(shù)?

上面提到了,有兩個方法:

  • prestartAllCoreThreads()提前創(chuàng)建所有的核心線程
  • prestartCoreThread,提前創(chuàng)建一個核心線程,如果當前線程數(shù)量大于corePoolSize,則不創(chuàng)建

線程池異常退出與自動退出的區(qū)別?

如果線程是由于程序異常導致的退出,那么completedAbruptly為true,如下代碼會再新建一個Worker線程。

如果線程是系統(tǒng)自動退出,即completedAbruptly為false的話,會根據(jù)配置判斷當前可以允許的最小核心線程數(shù)量

  • 配置allowCoreThreadTimeOut為true的話,最小核心線程數(shù)可以為0。
  • 默認情況下最小線程數(shù)為corePoolSize
int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }

線程池shutdown與shutdownNow有什么區(qū)別?

看代碼主要三個區(qū)別:

  • shutdown會把線程池的狀態(tài)改為SHUTDOWN,而shutdownNow把當前線程池狀態(tài)改為STOP
  • shutdown只會中斷所有空閑的線程,而shutdownNow會中斷所有的線程。
  • shutdown返回方法為空,會將當前任務隊列中的所有任務執(zhí)行完畢;而shutdownNow把任務隊列中的所有任務都取出來返回。
image-20200212093527721.png

最后

學無止境,還有很多細節(jié),但足以打動面試官,覺得真是一個很用心的候選人呢... 希望這些能幫到你。

還有阿里內推:這邊是阿里集團-淘系技術部的,總裁帶頭發(fā)起項目,新成立部門,業(yè)務急速擴張,目前還有大量的HC,機會多,考慮的聯(lián)系我:

郵箱:aihe.ah@alibaba-inc.com

微信:aihehe5211

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容