日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

背會了常見的幾個線程池用法,結果被問翻

來源: 責編: 時間:2024-01-15 09:21:23 210觀看
導讀背景這是張小帥失業之后的第三場面試。面試官:“實際開發中用過多線程吧,那聊聊線程池吧”?!坝蠧achedThreadPool:可緩存線程池,FixedThreadPool:定長線程池.......balabala”。小帥暗暗竊喜,還好把這幾種線程池背下來

背景

這是張小帥失業之后的第三場面試。EOE28資訊網——每日最新資訊28at.com

面試官:“實際開發中用過多線程吧,那聊聊線程池吧”。EOE28資訊網——每日最新資訊28at.com

“有CachedThreadPool:可緩存線程池,FixedThreadPool:定長線程池.......balabala”。小帥暗暗竊喜,還好把這幾種線程池背下來了,看來這次可以上岸了。EOE28資訊網——每日最新資訊28at.com

面試官點點頭,繼續問到“那線程池底層是如何實現復用的?”EOE28資訊網——每日最新資訊28at.com

“額,這個....”EOE28資訊網——每日最新資訊28at.com

寒風中,那個男人的背影在暮色中顯得孤寂而凄涼,仿佛與世隔絕,獨自面對著無盡的寂寞......EOE28資訊網——每日最新資訊28at.com

EOE28資訊網——每日最新資訊28at.com

概要

如果問到線程池的話,不好好剖析過底層代碼,恐怕真的會像小帥那樣被問翻吧。EOE28資訊網——每日最新資訊28at.com

那么在此我們就來好好剖析一下線程池的底層吧。我們大概從如下幾個方面著手:EOE28資訊網——每日最新資訊28at.com

EOE28資訊網——每日最新資訊28at.com

概覽圖EOE28資訊網——每日最新資訊28at.com

什么是線程池

說到線程池,其實我們要先聊到池化技術。EOE28資訊網——每日最新資訊28at.com

池化技術:我們將資源或者任務放入池子,使用時從池中取,用完之后交給池子管理。通過優化資源分配的效率,達到性能的調優。EOE28資訊網——每日最新資訊28at.com

池化技術優點:EOE28資訊網——每日最新資訊28at.com

  • 資源被重復使用,減少了資源在分配銷毀過程中的系統的調度消耗。比如,在IO密集型的服務器上,并發處理過程中的子線程或子進程的創建和銷毀過程,帶來的系統開銷將是難以接受的。所以在業務實現上,通常把一些資源預先分配好,如線程池,數據庫連接池,Redis連接池,HTTP連接池等,來減少系統消耗,提升系統性能。
  • 池化技術分配資源,會集中分配,這樣有效避免了碎片化的問題。
  • 可以對資源的整體使用做限制,相關資源預分配且只在預分配后生成,后續不再動態添加,從而限制了整個系統對資源的使用上限。

所以我們說線程池是提升線程可重復利用率、可控性的池化技術的一種。EOE28資訊網——每日最新資訊28at.com

線程池的使用

1.多線程發送郵件案例

現在我們有這樣一個場景,上層有業務系統批量調用底層進行發送郵件,廢話不多,直接上代碼:EOE28資訊網——每日最新資訊28at.com

EOE28資訊網——每日最新資訊28at.com

demoEOE28資訊網——每日最新資訊28at.com

最終運行輸出結果為:EOE28資訊網——每日最新資訊28at.com

由線程:pool-1-thread-1 發送第:0封郵件由線程:pool-1-thread-2 發送第:1封郵件由線程:pool-1-thread-1 發送第:2封郵件由線程:pool-1-thread-2 發送第:3封郵件由線程:pool-1-thread-1 發送第:4封郵件由線程:pool-1-thread-1 發送第:6封郵件由線程:pool-1-thread-2 發送第:5封郵件由線程:pool-1-thread-1 發送第:7封郵件由線程:pool-1-thread-2 發送第:8封郵件由線程:pool-1-thread-1 發送第:9封郵件

上面的例子中從結果來看是10封郵件分別由兩條線程發送出去了,上圖可見,我們給ThreadPoolExecutor這個執行器分別指定了七個參數。那么參數的含義到底是什么呢?接下來咱們層層抽絲剝繭。EOE28資訊網——每日最新資訊28at.com

2.構造函數說明

大家估計會有疑問,線程池的種類那么多,案例中為什么要用TheadPoolExecutor類呢,其他的種類是由TheadPoolExecutor通過不同的入參定義出來的,所以我們直接拿ThreadPoolExecutor來看。EOE28資訊網——每日最新資訊28at.com

我們先來看一下ThreadPoolExecutor的繼承關系,有個宏觀印象:EOE28資訊網——每日最新資訊28at.com

EOE28資訊網——每日最新資訊28at.com

宏觀繼承EOE28資訊網——每日最新資訊28at.com

我們再來看一下ThreadPoolExecutor的構造方法:EOE28資訊網——每日最新資訊28at.com

EOE28資訊網——每日最新資訊28at.com

構造方法EOE28資訊網——每日最新資訊28at.com

下面我們來解釋一下幾個參數的含義:EOE28資訊網——每日最新資訊28at.com

  • corePoolSize:核心線程數。
  • maximumPoolSize:最大線程數。
  • keepAliveTime:線程池中線程的最大閑置生命周期。
  • unit:針對keepAliveTime的時間單位。
  • workQueue:阻塞隊列。
  • threadFactory:創建線程的線程工廠。
  • handler:拒絕策略。

大家對上述的含義初步有個概念。EOE28資訊網——每日最新資訊28at.com

3.工作流程概述

看了上面的構造函數字段大家估計也還是優點懵的,尤其是從來沒有接觸過商品池的小伙伴。所以老貓又擼了一張商品池的大概的工作流程圖,方便大家把這些概念串起來。EOE28資訊網——每日最新資訊28at.com

EOE28資訊網——每日最新資訊28at.com

大概流程EOE28資訊網——每日最新資訊28at.com

上圖中老貓標記了四條線,簡單介紹一下(當然上圖若有問題,也希望大家能夠指出來)。EOE28資訊網——每日最新資訊28at.com

  • 當發起任務時候,會計算線程池中存在的線程數量與核心線程數量(corePoolSize)進行比較,如果小于,則在線程池中創建線程,否則,進行下一步判斷。
  • 如果不滿足條件1,則會將任務添加到阻塞隊列中。等待線程池中的線程空閑下來后,獲取隊列中的任務進行執行。
  • 但是條件2中如果阻塞隊列滿了之后,此時又會重新獲取當前線程的數量和最大線程數(maximumPoolSize)進行比較,如果發現小于最大線程數,那么繼續添加到線程池中即可。
  • 如果都不滿足上述條件,那么此時會放到拒絕策略中。

4.execute核心流程剖析

接下來我們來看一下執行theadPoolExecutor.execute()的時候到底發生了什么。先來看一下源碼:EOE28資訊網——每日最新資訊28at.com

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

(1) ctl變量EOE28資訊網——每日最新資訊28at.com

進入執行源碼之后我們首先看到的是ctl,只知道ctl中拿到了一個int數據至于這個數值有什么用,目前不知道,接著看涉及的相關代碼,老貓將相關的代碼解讀放到源碼中進行注釋。EOE28資訊網——每日最新資訊28at.com

    //通過ctl獲取線程池的狀態以及包含的線程數量    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));    private static final int COUNT_BITS = Integer.SIZE - 3;   // COUNT_BITS = 32-3 = 29    /**001左移29位     * 00100000 00000000 00000000 00000000     * 操作減1     * 00011111 11111111 11111111 11111111(表示初始化的時候線程情況,1表示均有空閑線程)     * 換成十進制:COUNT_MASK = 536870911     */    private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;    /**     * 運行中狀態     * 1的原碼     * 00000000 00000000 00000000 00000001     * 取反+1     * 11111111 11111111 11111111 11111111     * 左移29位     * 11100000 00000000 00000000 00000000     **/    // runState is stored in the high-order bits    private static final int RUNNING    = -1 << COUNT_BITS; //運行中狀態  11100000 00000000 00000000 00000000    private static final int SHUTDOWN   =  0 << COUNT_BITS; //終止狀態    00000000 00000000 00000000 00000000    private static final int STOP       =  1 << COUNT_BITS; //停止       00100000 00000000 00000000 00000000    private static final int TIDYING    =  2 << COUNT_BITS; //           01000000 00000000 00000000 00000000    private static final int TERMINATED =  3 << COUNT_BITS; //           01100000 00000000 00000000 00000000        //取高3位表示獲取運行狀態    private static int runStateOf(int c)     { return c & ~COUNT_MASK; }  //~COUNT_MASK表示取反碼:11100000 00000000 00000000 00000000    //取出低位29位的值,當前活躍的線程數    private static int workerCountOf(int c)  { return c & COUNT_MASK; } //COUNT_MASK:00011111 11111111 11111111 11111111    //計算ctl的值,ctl=[3位]線程池狀態 + [29位]線程池中線程數量。    private static int ctlOf(int rs, int wc) { return rs | wc; } //進行或運算

上面我們針對各個狀態以及那么多的二進制表示符有點懵,當然如果不會二進制運算的,大家可以先自己去了解一下二進制的運算邏輯。通過源碼中的英文,我們知道CTL的值其實分成兩部分組成,高三位是狀態,其余均為當前線程數。如下的圖:EOE28資訊網——每日最新資訊28at.com

EOE28資訊網——每日最新資訊28at.com

線程池狀態EOE28資訊網——每日最新資訊28at.com

上面的圖的描述解釋,其實也都是英文注釋版的翻譯,我們再來看一下有了這些狀態,這些狀態是怎么流轉的,英文注釋是這樣的:EOE28資訊網——每日最新資訊28at.com

/*** RUNNING -> SHUTDOWN     *    On invocation of shutdown()     * (RUNNING or SHUTDOWN) -> STOP     *    On invocation of shutdownNow()     * SHUTDOWN -> TIDYING     *    When both queue and pool are empty     * STOP -> TIDYING     *    When pool is empty     * TIDYING -> TERMINATED     *    When the terminated() hook method has completed     * /

上面的描述不太直觀,老貓將流程串了起來,得到了下面的狀態機流轉圖。如下圖:EOE28資訊網——每日最新資訊28at.com

EOE28資訊網——每日最新資訊28at.com

狀態機流程EOE28資訊網——每日最新資訊28at.com

寫到這里,其實ctl已經很清楚了,ctl說白了就是狀態位和活躍線程數的表示方式。通過ctl咱們可以知道當前是什么狀態以及活躍線程數量是多少 (設計很巧妙,如果此處還有問題,歡迎大家私聊老貓)。EOE28資訊網——每日最新資訊28at.com

(3) 線程池中的線程數小于核心線程數EOE28資訊網——每日最新資訊28at.com

讀完ctl之后,我們來看一下接下來的代碼。EOE28資訊網——每日最新資訊28at.com

if (workerCountOf(c) < corePoolSize) {            if (addWorker(command, true)) return; //添加新的線程            c = ctl.get(); //重新獲取當前的狀態以及線程數量}

繼上述的workerCountOf,我們知道這個方法可以獲取當前活躍的線程數。如果當前線程數小于配置的核心線程數,則會調用addWorker進行添加新的線程。如果添加失敗了,則重新獲取ctl的值。EOE28資訊網——每日最新資訊28at.com

(4) 任務添加到隊列的相關邏輯EOE28資訊網——每日最新資訊28at.com

if (isRunning(c) && workQueue.offer(command)) {            int recheck = ctl.get();            //再次check一下,當前線程池是否是運行狀態,如果不是運行時狀態,則把剛剛添加到workQueue中的command移除掉            if (! isRunning(recheck) && remove(command))                reject(command);            else if (workerCountOf(recheck) == 0)                addWorker(null, false);        }

上述我們知道當添加線程池失敗的時候,我們會重新獲取ctl的值。此時咱們的第一步就很清楚了:EOE28資訊網——每日最新資訊28at.com

  • 通過isRunning方法來判斷線程池狀態是不是運行中狀態,如果是,則將command任務放到阻塞隊列workQueue中。
  • 再次check一下,當前線程池是否是運行狀態,如果不是運行時狀態,則把剛剛添加到workQueue中的command移除掉,并調用拒絕策略。否則,判斷如果當前活動的線程數如果為0,則表明只去創建線程,而此處,并不執行任務(因為,任務已經在上面的offer方法中被添加到了workQueue中了,等待線程池中的線程去消費隊列中的任務)

(5) 線程池中的線程數量小于最大線程數代碼邏輯以及拒絕策略的代碼邏輯EOE28資訊網——每日最新資訊28at.com

接下來,我們看一下最后的一個步驟EOE28資訊網——每日最新資訊28at.com

/** * 進入第三步驟前提: * 1.線程池不是運行狀態,所以isRunning(c)為false * 2.workCount >= corePoolSize的時候于此同時并且添加到queue失敗的時候執行 */else if (!addWorker(command, false))            reject(command);    }

由于調用addWorker的第二個參數是false,則表示對比的是最大線程數,那么如果往線程池中創建線程依然失敗,即addWorker返回false,那么則進入if語句中,直接調用reject方法調用拒絕策略了。EOE28資訊網——每日最新資訊28at.com

寫到這里大家估計會對這個第二個參數是false為什么比較的是最大線程數有疑問。其實這個是addWorker中的方法。我們可以大概看一下:EOE28資訊網——每日最新資訊28at.com

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

我們很明顯地看到當core為flase的時候咱們獲取的是maximumPoolSize,也就是最大線程數。EOE28資訊網——每日最新資訊28at.com

寫到這里,其實咱們的核心主流程大概就已經結束了。這里其實老貓也只是寫了一個算是比較入門的開頭。當然我們還可以再深入去理addWorker的源碼。這個其實就交給大家去細看了,篇幅過長,相信大家也會失去閱讀的興趣了,感興趣的可以自己研究一下,如果說還是有問題的,可以找老貓一起探討,老貓的公眾號:"程序員老貓"。老貓覺得在上述的源碼中比較重要的其實就是ctl值的流轉順序以及計算方式,讀懂這個的話,后面一切的源碼只要順藤摸瓜即可理解。EOE28資訊網——每日最新資訊28at.com

5.Executors線程池模板

我們上述主要和大家分享了比較核心的theadPoolExecutor。除此之外,線程池Executors里面包含了很多其他的線程池模板。當然這也是小貓直接面試的時候說的那些,其實小貓也就僅僅只是背了線程池模板而已,并不知曉其工作原理。如下幾種:EOE28資訊網——每日最新資訊28at.com

  • newCachedThreadPool 創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。
  • newFixedThreadPool 創建一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。
  • newScheduledThreadPool 創建一個定長線程池,支持定時及周期性任務執行。
  • newSingleThreadScheduleExecutor 創建一個單線程執行程序,它可安排在給定延遲后運行命令或者定期地執行。(注意,如果因為在關閉前的執行期間出現失敗而終止了此單個線程,那么如果需要,一個新線程會代替它執行后續的任務)。可保證順序地執行各個任務,并且在任意給定的時間不會有多個線程是活動的。與其他等效的 newScheduledThreadPool(1) 不同,可保證無需重新配置此方法所返回的執行程序即可使用其他的線程。
  • newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

6.多樣化的blockingQueue

  • PriorityBlockingQueue 它是一個無界的并發隊列。無法向這個隊列中插入null值。所有插入到這個隊列中的元素必須實現Comparable接口。因此該隊列中元素的排序就取決于你自己的Comparable實現。
  • SynchronousQueue 它是一個特殊的隊列,它的內部同時只能夠容納單個元素。如果該隊列已有一個元素的話,那么試圖向隊列中插入一個新元素的線程將會阻塞,直到另一個新線程將該元素從隊列中抽走。同樣的,如果隊列為空,試圖向隊列中抽取一個元素的線程將會被阻塞,直到另一個線程向隊列中插入了一條新的元素。因此,它其實不太像是一個隊列,而更像是一個匯合點。
  • ArrayBlockingQueue 它是一個有界的阻塞隊列,其內部實現是將對象放到一個數組里。一但初始化,大小就無法修改
  • LinkedBlockingQueue 它內部以一個鏈式結構(鏈接節點)對其元素進行存儲。可以指定元素上限,否則,上限則為Integer.MAX_VALUE。
  • DelayQueue 它對元素進行持有直到一個特定的延遲到期。注意:進入其中的元素必須實現Delayed接口。

上述針對這些羅列了一下,其實很多官網上也有相關的介紹,當然感興趣的小伙伴也可以再去刨一刨里面的源碼實現。EOE28資訊網——每日最新資訊28at.com

7.拒絕策略

  • AbortPolicy 丟棄任務并拋出RejectedExecutionException異常。
  • DiscardPolicy 丟棄任務,但是不拋出異常。
  • DiscardOldestPolicy 丟棄隊列中最前面的任務,然后重新嘗試執行任務。
  • CallerRunsPolicy 由調用線程處理該任務。

總結

很多小伙伴在用一些線程池或者第三方中間件的時候可能只停留在如何使用上,一旦出了問題或者被人深入問到其實現原理的時候就比較頭大。所以在日常開發的過程中,我們不僅僅需要知道如何去用,其實更應該知道底層的原理是什么。這樣才能長立于不敗之地。EOE28資訊網——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-60967-0.html背會了常見的幾個線程池用法,結果被問翻

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 面試官:實際工作中哪里用到了自定義注解?

下一篇: MXNet的多語言支持和高效的分布式訓練功能有哪些優勢?

標簽:
  • 熱門焦點
  • 7月安卓手機好評榜:三星S23Ultra好評率第一

    性能榜和性價比榜之后,我們來看最后的安卓手機好評榜,數據來源安兔兔評測,收集時間2023年7月1日至7月31日,僅限國內市場。第一名:三星Galaxy S23 Ultra好評率:95.71%在即將迎來新
  • 7月安卓手機性能榜:紅魔8S Pro再奪榜首

    7月份的手機市場風平浪靜,除了紅魔和努比亞帶來了兩款搭載驍龍8Gen2領先版處理器的新機之外,別的也想不到有什么新品了,這也正常,通常6月7月都是手機廠商修整的時間,進入8月份之
  • 6月安卓手機好評榜:魅族20 Pro蟬聯冠軍

    性能榜和性價比榜之后,我們來看最后的安卓手機好評榜,數據來源安兔兔評測,收集時間2023年6月1日至6月30日,僅限國內市場。第一名:魅族20 Pro好評率:95%5月份的時候魅族20 Pro就是
  • K6:面向開發人員的現代負載測試工具

    K6 是一個開源負載測試工具,可以輕松編寫、運行和分析性能測試。它建立在 Go 和 JavaScript 之上,它被設計為功能強大、可擴展且易于使用。k6 可用于測試各種應用程序,包括 Web
  • 2023 年的 Node.js 生態系統

    隨著技術的不斷演進和創新,Node.js 在 2023 年達到了一個新的高度。Node.js 擁有一個龐大的生態系統,可以幫助開發人員更快地實現復雜的應用。本文就來看看 Node.js 最新的生
  • 十個簡單但很有用的Python裝飾器

    裝飾器(Decorators)是Python中一種強大而靈活的功能,用于修改或增強函數或類的行為。裝飾器本質上是一個函數,它接受另一個函數或類作為參數,并返回一個新的函數或類。它們通常用
  • 這款新興工具平臺,讓你的電腦效率翻倍

    隨著信息技術的發展,我們獲取信息的渠道越來越多,但是處理信息的效率卻成為一個瓶頸。于是各種工具應運而生,都在爭相解決我們的工作效率問題。今天我要給大家介紹一款效率
  • iQOO 11S屏幕細節公布:首發三星2K E6全感屏 安卓最好的直屏手機

    日前iQOO手機官方宣布,新一代電競旗艦iQOO 11S將會在7月4日19:00正式與大家見面。隨著發布時間的日益臨近,官方關于該機的預熱也更加密集,截至目前已
  • 榮耀Magic4 至臻版 首創智慧隱私通話 強勁影音系統

    2022年第一季度臨近尾聲,在該季度內,許多品牌陸續發布自己的最新產品,讓大家從全新的角度來了解當今的手機技術。手機是電子設備中,更新迭代十分迅速的一款產品,基
Top 主站蜘蛛池模板: 清河县| 阜南县| 赤壁市| 江安县| 阿城市| 上蔡县| 永春县| 宜阳县| 满城县| 铁力市| 西宁市| 丹凤县| 平乡县| 海阳市| 大港区| 舟曲县| 龙口市| 咸丰县| 凤翔县| 五台县| 广东省| 彝良县| 金塔县| 离岛区| 和平区| 临漳县| 正阳县| 高尔夫| 额济纳旗| 陇川县| 靖西县| 新丰县| 土默特左旗| 南雄市| 吴桥县| 双桥区| 胶南市| 布尔津县| 马关县| 特克斯县| 额济纳旗|