並發編程之:線程池(一)


大家好,我是小黑,一個在互聯網苟且偷生的農民工。

池化

線程池是在計算機開發中常見的一種池化技術,是為了提高資源的利用率,將一些資源重復利用,避免重復的構建來提高效率。類似字符串常量池,數據庫連接池,HttpClient連接池等,都是用的池化技術。

線程池

在沒有線程池概念之前,我們要使用線程必須先通過創建一個Thread類來完成線程的構建,並調用start()方法開啟,在線程執行完會將線程銷毀,而線程資源是很寶貴的,創建和銷毀線程會造成資源的浪費。而線程池是將創建的線程存儲到一個池中,在需要使用時從池中去拿,使用完之后再講線程歸還到池中,下一次接着使用。

舉個栗子,好比我們去銀行辦理業務時,銀行會有窗口為客戶辦理業務,如果沒有線程池,就好比每次來一個客戶,銀行都打開一個窗口,辦理完業務之后將窗口關閉,這樣確實很浪費時間,所以銀行會默認開幾個窗口,比如三個,等客戶來辦理業務;一個客戶辦理完,下一個客戶可以繼續在這個窗口辦理。

核心線程數

在線程池初始化時,會指定創建核心線程的數量,有任務提交給線程池時,先判斷是否有空閑線程,如果有空閑線程,則直接使用,如果沒有則看當前線程池中的數量是不是小於核心線程數,如果是則創建新的線程,如果已經到達核心線程數,則需要做下一步操作。

等待隊列

下一步操作就是要進入等待隊列,等待隊列好比是去銀行辦業務時沒有空閑窗口,需要坐在大廳的座椅上排隊;線程池也是一樣,如果沒有核心線程,則需要將任務放入等待隊列,等待有空閑線程再執行。

最大線程數

那我們都知道銀行有時候人特別多的時候,會增加窗口,一般是當大廳的座椅坐滿人時,窗口都很緊張處理不過來,這是會增加窗口;線程池也是一樣,如果等待的任務已經放滿了等待隊列,並且核心線程都在繁忙,這時會查看線程池中的線程數量是否到達最大線程數,如果沒有則會創建新的線程來繼續處理。

image-20210903211946836

拒絕策略

那如果說,線程池中的線程已經到達最大線程數並且都在繁忙,還有新的任務進來,好比銀行已經坐滿人了,窗口也都在忙,客戶都排到門口了,這時要是還有人要辦理,應該怎么處理呢?銀行一般會讓這個人先回家,改天再來辦,或者如果是個大客戶,銀行可能會單獨帶去VIP辦公室辦理等等。線程池在這種情況下也有相應的處理方式,這種處理方式我們稱之為拒絕策略,如果會放任務在當前線程執行,或者直接將任務丟棄等等,在后面的章節中我會詳細給大家介紹。

JDK中的線程池

在JDK中提供了相應的API來創建線程池,這些API也是在我們最近講到過的JUC包中。

image-20210903212843343

Executor

首先,線程池需要具備能夠執行任務的能力,這個任務通過一個線程來處理。而這個執行任務的能力通過Executor接口來約定。

public interface Executor {
    void execute(Runnable command);
}

ExecutorService

在某些場景我們需要知道任務執行完之后的結果,拿到返回值,而Runnable接口是沒有返回值的;以及一些可以關閉線程池中的線程,執行線程中的任務的方法,定義在ExecutorService接口中。

image-20210903221251283

ThreadPoolExecutor

ThreaPoolExecutor則是對ExecutorService的具體實現類,通過ThreaPoolExecutor類可以創建出一個線程池,我們先來看一下代碼。

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    // 核心線程數 corePoolSize
    3, 
    // 最大線程數 maximumPoolSize
    5, 
    // 空閑線程保留存活的時間和時間單位
    10, TimeUnit.SECONDS, 
    // 等待隊列
    new ArrayBlockingQueue<>(3), 
    // 創建線程的工廠
    Executors.defaultThreadFactory(), 
    // 拒絕策略
    new ThreadPoolExecutor.AbortPolicy() 
);

從代碼我們可以看出,創建一個線程池,需要指定我們上面說到的核心線程數,最大線程數,等待隊列,拒絕策略等,並且還要指定創建線程的工廠對象。

當然ThreadPoolExecutor也有其他的構造方法,可以不顯式指定拒絕策略和工廠對象。

new ThreadPoolExecutor(3,5,10,TimeUnit.SECONDS,new ArrayBlockingQueue<>(3));
// 構造方法
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    // 使用默認的線程工廠和默認的拒絕策略。
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler); 
}

7個線程池參數

  • corePoolSize:核心線程數
  • maximumPoolSize:最大線程數
  • keepAliveTime:空閑線程保持存活時間
  • unit:空閑線程保持存活時間單位
  • workQueue:等待隊列
  • threadFactory:線程創建工廠
  • RejectedExecutionHandler:拒絕策略

4種拒絕策略

這里我們說一下4中拒絕策略。接口RejectedExecutionHandler定義了拒絕策略,所有的拒絕策略都需要實現該接口。

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

在ThreadPoolExecutor類中定義了4個拒絕策略的具體實現。

  • AbortPolicy:拒絕處理,拋出異常
  • CallerRunsPolicy:由創建該線程的線程(main)執行
  • DiscardPolicy: 丟棄,不拋出異常
  • DiscardOldestPolicy:和最早創建的線程進行競爭,不拋出異常

可通過如下方式進行拒絕策略的創建。

// 拒絕處理,拋出異常
new ThreadPoolExecutor.AbortPolicy(); 
// 由創建該線程的線程(main)執行
new ThreadPoolExecutor.CallerRunsPolicy();  
// 丟棄,不拋出異常
new ThreadPoolExecutor.DiscardPolicy(); 
// 和最早創建的線程進行競爭,不拋出異常
new ThreadPoolExecutor.DiscardOldestPolicy(); 

4種線程池種類

那么具體我們在使用時應該創建怎樣的線程池呢?在JDK的Executors工具類為我們提供了4種線程池的創建方式。

// 只有一個線程
Executors.newSingleThreadExecutor(); 
// 固定線程數
Executors.newFixedThreadPool(5); 
// 可伸縮的
Executors.newCachedThreadPool(); 
// 可延遲執行,使用優先隊列DelayedWorkQueue
Executors.newScheduledThreadPool(3);

小結

好的,通過今天的內容我們先對線程池的使用有一個初步的了解,下期內容再跟大家深入解析一下線程池中的具體實現原理。

本期內容就到這里,我們下期見。
關注公眾號【小黑說Java】干貨不斷。
image


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM