面試刷題36:線程池的原理和使用方法?

来源:https://www.cnblogs.com/snidget/archive/2020/04/11/12683177.html
-Advertisement-
Play Games

線程池原理和使用在面試中被高頻問到,比如阿裡的面試題。下麵我們針對問題來進行回答。 為什麼要使用線程池? 線程池的使用場景有2: 1, 高併發場景:比如tomcat的處理機制,內置了線程池處理http請求; 2,非同步任務處理:比如spring的非同步方法改造,增加@Asyn註解對應了一個線程池; 使用 ...


image.png

線程池原理和使用在面試中被高頻問到,比如阿裡的面試題。下麵我們針對問題來進行回答。

為什麼要使用線程池?

線程池的使用場景有2:

1, 高併發場景:比如tomcat的處理機制,內置了線程池處理http請求;

2,非同步任務處理:比如spring的非同步方法改造,增加@Asyn註解對應了一個線程池;

使用線程池帶來的好處有4:

1, 降低系統的消耗:線程池復用了內部的線程對比處理任務的時候創建線程處理完畢銷毀線程降低了線程資源消耗

2,提高系統的響應速度:任務不必等待新線程創建,直接復用線程池的線程執行

3,提高系統的穩定性:線程是重要的系統資源,無限制創建系統會奔潰,線程池復用了線程,系統會更穩定

4,提供了線程的可管理功能:暴露了方法,可以對線程進行調配,優化和監控

線程池的實現原理

線程池處理任務流程

當向線程池中提交一個任務,線程池內部是如何處理任務的?

先來個流程圖,標識一下核心處理步驟:

file

1,線程池內部會獲取activeCount, 判斷活躍線程的數量是否大於等於corePoolSize(核心線程數量),如果沒有,會使用全局鎖鎖定線程池,創建工作線程,處理任務,然後釋放全局鎖;

2,判斷線程池內部的阻塞隊列是否已經滿了,如果沒有,直接把任務放入阻塞隊列;

3,判斷線程池的活躍線程數量是否大於等於maxPoolSize,如果沒有,會使用全局鎖鎖定線程池,創建工作線程,處理任務,然後釋放全局鎖;

4,如果以上條件都滿足,採用飽和處理策略處理任務。

說明:使用全局鎖是一個嚴重的可升縮瓶頸,線上程池預熱之後(即內部線程數量大於等於corePoolSize),任務的處理是直接放入阻塞隊列,這一步是不需要獲得全局鎖的,效率比較高。

源碼如下:

    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);
    }

註釋沒保留,註釋的內容就是上面畫的流程圖;
代碼的邏輯就是流程圖中的邏輯。

線程池中的線程執行任務

執行任務模型如下:

image.png

線程池中的線程執行任務分為以下兩種情況:

1, 創建一個線程,會在這個線程中執行當前任務;

2,工作線程完成當前任務之後,會死迴圈從BlockingQueue中獲取任務來執行;

代碼如下:

  private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (int c = ctl.get();;) {
            // Check if queue empty only if necessary.
            if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP)
                    || firstTask != null
                    || workQueue.isEmpty()))
                return false;

            for (;;) {
                if (workerCountOf(c)
                    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateAtLeast(c, SHUTDOWN))
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int c = ctl.get();

                    if (isRunning(c) ||
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    //釋放鎖
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //執行提交的任務,然後設置工作線程為啟動狀態
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

從代碼中可以看到:把工作線程增加到線程池,然後釋放鎖,執行完提交進來的任務之後,新建的工作線程狀態為啟動狀態;

線程池的使用

創建線程池

創建線程池使用線程池的構造函數來創建。

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 

參數簡單翻譯過來,然後做一下備註:

file

RejectedExecutionHandler分為4種:

Abort:直接拋出異常

Discard:靜默丟棄最後的任務

DiscardOldest:靜默丟棄最先入隊的任務,並處理當前任務

CallerRuns:調用者線程來執行任務

也可以自定義飽和策略。實現RejectedExecutionHandler即可。

線程池中提交任務

線程池中提交任務的方法有2:
1,void execute(Runable) ,沒有返回值,無法判斷任務的執行狀態。

2,Future submit(Callable) ,有返回值,可以根據返回的Future對象來判斷任務的執行狀態,也可以調用get方法來同步阻塞當前線程獲取結果,或者採用get方法的超時版本,防止阻塞超時的發生。

代碼如下:

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}
    <T> Future<T> submit(Callable<T> task);

關閉線程池

關閉線程池方法有2:

1,shutdown();

2,shutdownNow();

兩種關閉的方法區別如下表:

file

關閉原理都是調用線程的interrupt()方法來中斷所有的工作線程,所以無法中斷的線程的任務可能永遠沒法終止。

只要調用了以上兩個方法,isShutdown=true;只有所有的工作線程都關閉,isTerminaed=true;

如何合理的配置線程池參數?

分如下場景,參考選擇依據如下:

file

隊列的使用推薦使用有界隊列,提高系統的穩定性和預警能力。

監控線程池

場景:當線程池出現問題,可以根據監控數據快速定位和解決問題。

線程池提供的主要監控參數:

file

也可以自定義監控,通過自定義線程池,實現beforeExecute,afterExecute,terminated方法,可以在任務執行前,任務執行後,線程池關閉前記錄監控數據。

小結

本篇先從使用場景和優點出發分析了為什麼要使用線程池。

然後介紹了線程池中任務的執行過程,以及工作線程處理任務的兩種方式。

最後介紹瞭如何使用線程池,創建,銷毀,提交任務,監控,設置合理的參數調優等方面。

原創不易,點贊關註支持一下吧!轉載請註明出處,讓我們互通有無,共同進步,歡迎溝通交流。
我會持續分享Java軟體編程知識和程式員發展職業之路,歡迎關註,我整理了這些年編程學習的各種資源,關註公眾號‘李福春持續輸出’,發送'學習資料'分享給你!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 作為一個碼農,你知道如何啟動一個java線程嗎? 啟動線程 public class PrintThread extends Thread { public void run() { System.out.println("我是線程! 繼承自Thread"); } public static voi ...
  • 全民閱讀的時代已經來臨,目前使用讀書軟體的用戶數2.1億,日活躍用戶超過500萬,其中19-35歲年輕用戶占比超過60%,本科及以上學歷用戶占比高達80%,北上廣深及其他省會城市/直轄市用戶占比超過80%。**本人習慣使用微信讀書,為了方便整理書籍和導出筆記,便開發了這個小工具。** ...
  • 前言 在【JAVA進階架構師指南】系列二和三中,我們瞭解了JVM的記憶體模型以及類載入機制,其中在記憶體模型中,我們說到,從線程角度來說,JVM分為線程私有的區域(虛擬機棧/本地方法棧/程式計數器)和線程公有區域(方法區和java堆),其中線程私有區域記憶體隨著線程的結束而跟著被回收,GC主要關註的是堆和 ...
  • 靈魂拷問:在不重啟服務的前提下,如何讓配置修改生效的呢?有什麼奇技淫巧嗎? 靈魂拷問:在 Java 項目中,總能看到以 .properties 為尾碼的文件蹤影,這類配置文件是怎麼載入的呢? 項目研發過程中,總會遇到一些經常改變的參數,比如要連接的資料庫的連接地址、名稱、用戶名、密碼;再比如訪問三方 ...
  • 1、什麼是字元串? Go語言中字元串是一個位元組切片。把內容放在雙引號""之間,我們可以創建一個字元串,讓我們來看一下創建並列印字元串的簡單示例。 package main import ( "fmt" ) func main() { str := "hello golang" fmt.Println ...
  • 一、Java註解 1.引入起始:Java5.0開始引入; 2.該功能可用於類、構造方法、成員變數、方法、參數 3.註解功能的影響範圍:不影響程式的正常執行,但是會對編譯器等輔助工具產生影響。 4.定義:註解又可以稱為標註,是程式的元數據,也是程式代碼的標記。 5.獲取方式:在編譯、載入類和運行時。 ...
  • PHP常用設計模式詳解 單例模式: php交流群:159789818 特性:單例類只能有一個實例 類內__construct構造函數私有化,防止new實例 類內__clone私有化,防止複製對象 設置一個$instance私有靜態屬性,為了保存當前類的實例 設置一個getInstance公有方法,為 ...
  • 寫在前面 在JDK中,提供了這樣一種功能:它能夠將複雜的邏輯拆分成一個個簡單的邏輯來並行執行,待每個並行執行的邏輯執行完成後,再將各個結果進行彙總,得出最終的結果數據。有點像Hadoop中的MapReduce。 ForkJoin是由JDK1.7之後提供的多線程併發處理框架。ForkJoin框架的基本 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...