偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

小米面試:什么是線程池?工作原理是什么?線程池可以動態(tài)修改嗎?

開發(fā) 前端
線程池(Thread Pool)是一種基于池化思想管理線程的工具,它維護多個線程。在線程池中,總有幾個活躍線程。當(dāng)需要使用線程來執(zhí)行任務(wù)時,可以從池子中隨便拿一個空閑線程來用,當(dāng)完成工作時,該線程并不會死亡,而是再次返回線程池中成為空閑狀態(tài),等待執(zhí)行下一個任務(wù)。

大家好,我是碼哥,《Redis 高手心法》暢銷書作者。

有讀者分享小米 Java 后端面試,其中有一個問題,當(dāng)時沒有回答好:什么是線程池、工作原理是什么、線程池可以動態(tài)修改嗎?

回答這個問題之前,首先我們來了解下什么是線程池,它的工作原理是什么。

什么是線程池

線程池(Thread Pool)是一種基于池化思想管理線程的工具,它維護多個線程。在線程池中,總有幾個活躍線程。當(dāng)需要使用線程來執(zhí)行任務(wù)時,可以從池子中隨便拿一個空閑線程來用,當(dāng)完成工作時,該線程并不會死亡,而是再次返回線程池中成為空閑狀態(tài),等待執(zhí)行下一個任務(wù)。

這種做法,一方面避免了處理任務(wù)時創(chuàng)建銷毀線程開銷的代價,另一方面避免了線程數(shù)量膨脹導(dǎo)致的過分調(diào)度問題,保證了對內(nèi)核的充分利用。

線程池狀態(tài)

然后,我們來看下線程池有哪些狀態(tài)呢?

線程池有五種狀態(tài):這五種狀態(tài)并不能任意轉(zhuǎn)換,只會有以下幾種轉(zhuǎn)換情況:線程池的五種狀態(tài)是如何流轉(zhuǎn)的?

  • RUNNING:會接收新任務(wù)并且會處理隊列中的任務(wù)
  • SHUTDOWN:不會接收新任務(wù)并且會處理隊列中的任務(wù)
  • STOP:不會接收新任務(wù)并且不會處理隊列中的任務(wù),并且會中斷在處理的任務(wù)(注意:一個任務(wù)能不能被中斷得看任務(wù)本身)
  • TIDYING:所有任務(wù)都終止了,線程池中也沒有線程了,這樣線程池的狀態(tài)就會轉(zhuǎn)為 TIDYING,一旦達到此狀態(tài),就會調(diào)用線程池的 terminated()
  • TERMINATED:terminated()執(zhí)行完之后就會轉(zhuǎn)變?yōu)?TERMINATED

圖片

線程池工作原理

如何自定義一個線程池?

public ThreadPoolExecutor threadPoolExecutor() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                // 核心線程池大小,表示線程池常駐線程數(shù)量
                30,
                // 最大線程數(shù),表示線程池最多創(chuàng)建的線程數(shù)量
                100,
                // ?;顣r間,表示一個非核心線程多久沒有使用,會被回收
                10,
                TimeUnit.MINUTES,
                // 阻塞隊列,表示隊列最多緩存多少任務(wù),如果隊列滿了,將觸發(fā) RejectedExecutionHandler
                new ArrayBlockingQueue<>(1000),
                // 線程工廠,創(chuàng)建線程時候用的,可以給線程命名等
                new NamedThreadFactory("cust-task")
        );
        // 拒絕策略,當(dāng)阻塞隊列滿了之后,會觸發(fā)這里的handler
        // 默認是丟棄新任務(wù)
        executor.setRejectedExecutionHandler((r, executor1) -> {
            log.warn("thread pool is full");
        });
    }

線程池執(zhí)行流程圖

圖片

  1. 首先檢測線程池運行狀態(tài),如果不是 RUNNING,則直接拒絕,線程池要保證在 RUNNING 的狀態(tài)下執(zhí)行任務(wù)。
  2. 如果當(dāng)前線程數(shù)未超過核心線程數(shù),則創(chuàng)建并啟動一個線程來執(zhí)行新提交的任務(wù)。
  3. 如果當(dāng)前線程數(shù)超過核心線程數(shù),且線程池內(nèi)的阻塞隊列未滿,則將任務(wù)添加到該阻塞隊列中。
  4. 如果當(dāng)前線程數(shù)超過核心線程數(shù)且 線程池內(nèi)的阻塞隊列已滿,且未超過最大線程數(shù),則創(chuàng)建并啟動一個線程來執(zhí)行新提交的任務(wù)。
  5. 如果已超過最大線程數(shù),并且線程池內(nèi)的阻塞隊列已滿, 則根據(jù)拒絕策略來處理該任務(wù), 默認的處理方式是直接拋異常。

注意:提交一個 Runnable 時,不管當(dāng)前線程池中的線程是否空閑,只要數(shù)量小于核心線程數(shù)就會創(chuàng)建新線程。

線程池的拒絕策略

圖片

ThreadPoolExecutor 內(nèi)部有實現(xiàn) 4 個拒絕策略:

  1. CallerRunsPolicy,由調(diào)用 execute 方法提交任務(wù)的線程來執(zhí)行這個任務(wù)。
  2. AbortPolicy,拋出異常 RejectedExecutionException 拒絕提交任務(wù)。
  3. DiscardPolicy,直接拋棄任務(wù),不做任何處理。
  4. DiscardOldestPolicy,去除任務(wù)隊列中的第一個任務(wù)(最舊的),重新提。

如何監(jiān)控線程池?

好了,言歸正傳,再回歸到這個題目本身,在修改線程池之前,我們要如何監(jiān)控線程池的信息呢?

比如線程池的執(zhí)行任務(wù)前后總時間,當(dāng)前任務(wù)數(shù)等信息。

  • 統(tǒng)計任務(wù)執(zhí)行時間可以通過實現(xiàn) beforeExecute 和 afterExecute 方法,計算出任務(wù)總耗時。

圖片

  • 統(tǒng)計線程池的任務(wù)數(shù),線程數(shù)等信息,可定時上報到 kafka,展示到可視化的界面上比如 Grafana。

圖片

監(jiān)控核心代碼

@Slf4j
public class ThreadPoolMonitor {

    private final ThreadPoolExecutor customThreadPool;
    private final String poolName;
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    public ThreadPoolMonitor(ThreadPoolExecutor customThreadPool, String poolName) {
        this.customThreadPool = customThreadPool;
        this.poolName = poolName;
    }

    public void startMonitoring(long period, TimeUnit unit) {
        scheduler.scheduleAtFixedRate(this::monitor, 0, period, unit);
    }

    private void monitor() {
        //核心線程數(shù)
        int corePoolSize = customThreadPool.getCorePoolSize();
        //最大線程數(shù)
        int maximumPoolSize = customThreadPool.getMaximumPoolSize();
        //活躍線程數(shù)
        int activeCount = customThreadPool.getActiveCount();
        //隊列任務(wù)數(shù)
        int queueSize = customThreadPool.getQueue().size();
        //已執(zhí)行完成任務(wù)數(shù)
        long completedTaskCount = customThreadPool.getCompletedTaskCount();
        //隊列任務(wù)數(shù)峰值
        int largestPoolSize = customThreadPool.getLargestPoolSize();

        //上報監(jiān)控數(shù)據(jù)
        sendToKafka(corePoolSize,maximumPoolSize, activeCount, queueSize, completedTaskCount, largestPoolSize);
    }

    private void sendToKafka(int corePoolSize,int maximumPoolSize, int activeCount, int queueSize, long completedTaskCount, int largestPoolSize) {
        // 自定義實現(xiàn)發(fā)送kafka邏輯或上報到prometheus邏輯
    }
}

如何動態(tài)調(diào)整線程池?

一般我們在設(shè)置線程池的線程數(shù)時,會參考實際業(yè)務(wù)場景。比較通用的公式是

  • IO 密集型場景:線程數(shù)=CPU 核心數(shù)*2+1
  • CPU 密集型場景線程數(shù)=CPU 核心數(shù)+1

但這只是比較簡單粗暴的計算方式,在實際使用過程中,我們還是不可避免的需要調(diào)整線程池的一些參數(shù),以達到最佳性能。

那么我們通過會比較關(guān)注線程池以下的幾個參數(shù)

線程池參數(shù)

說明

corePoolSize

核心線程數(shù)

maximumPoolSize

最大線程數(shù)

queueCapacity

等待隊列大小

keepAliveTime

空閑時間

  1. corePoolSize、maximumPoolSize 和 keepAliveTime 可以通過調(diào)用 setCorePoolSize、setMaximumPoolSize、setKeepAliveTime 方法修改。
  2. queueCapacity 雖然不能直接修改,我們可以通過實現(xiàn)自定義一個阻塞隊列的方式去實現(xiàn) setQueueCapacity 方法來修改隊列大小的屬性。

最后可以通過 Apollo、Nacos 配置中心實現(xiàn)動態(tài)監(jiān)聽的方法,達到實時更新線程池的效果。

擴展 1:線程池核心線程數(shù)會被銷毀嗎?

擴展 2:線程發(fā)生異常,會被移出線程池嗎?

責(zé)任編輯:武曉燕 來源: 碼哥跳動
相關(guān)推薦

2024-05-20 10:03:15

線程池優(yōu)先級隊列排序方法

2024-07-15 08:20:24

2022-03-02 07:36:37

池化技術(shù)Java線程池

2024-03-11 18:18:58

項目Spring線程池

2013-08-27 14:04:29

2022-09-13 07:50:26

小米面試官MySQL

2021-07-16 11:35:20

Java線程池代碼

2012-05-15 02:18:31

Java線程池

2020-12-10 08:24:40

線程池線程方法

2025-01-09 11:24:59

線程池美團動態(tài)配置中心

2024-11-27 08:15:50

2021-02-05 12:34:33

線程池系統(tǒng)

2020-03-05 15:34:16

線程池C語言局域網(wǎng)

2023-07-28 07:18:39

final繼承結(jié)構(gòu)

2020-04-29 14:10:44

Java線程池編程語言

2022-06-24 06:43:57

線程池線程復(fù)用

2022-03-21 07:40:08

線程池Executors方式

2023-05-19 08:01:24

Key消費場景

2022-03-14 08:02:08

輕量級動態(tài)線程池

2024-11-25 12:20:00

Hystrix微服務(wù)架構(gòu)
點贊
收藏

51CTO技術(shù)棧公眾號