面試刷題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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...