【線程池】ThreadPoolExecutor結構以及構造器參數詳解

線程池的介紹

不用線程池存在的問題

  • 反覆創建銷燬線程開銷大
  • 過多的線程佔用大量內存

線程池的使用,就類似於計劃經濟,控制資源總量,複用線程。有如下3點好處。

  1. 加快響應速度。消除了線程創建帶來的延時。
  2. 合理的利用CPU和內存。控制線程的數量,既不會因爲線程太多導致內存溢出,也不會因爲太少導致CPU資源的浪費。
  3. 便於統一管理線程。例如數據統計。

線程的使用場景舉例:

  • 服務器接收大量請求,使用線程池可以大大減少線程創建和銷燬次數,提高服務器工作效率。
  • 開發中使用多個線程時,可以考慮採用線程池。

線程池結構

在這裏插入圖片描述
ThreadPoolExecutor繼承AbstractExecutorService(實現ExecutorService),4個類/接口都可以視作線程池,並向上轉型(多態)。ThreadPoolExecutor中有5個嵌套類,其中4個爲RejectedExecutionHandler拒絕策略接口的實現類,1個是Worker類,用於維護正在運行任務的線程的中斷控制狀態,以及其他的次要信息。

Executors是線程池工具類,可以通過靜態工廠方法快捷實現線程池,例如Executors.newFixedThreadPool(10).

舉個麪包店的例子
假設有一所麪包店,麪包店平時有5位麪包師,每天隨着訂單增加,5位師傅逐漸加入到工作中去。不能及時處理的訂單,會掛在牆上的訂單欄上,等待麪包師順序處理。
訂單過多時,5個麪包師忙不過來就招臨時麪包師,最多5名。根據任務多少會調整臨時麪包師的數量,太長時間沒事幹的臨時麪包師會被辭退掉。
店鋪關閉或者訂單在麪包師達到10名後,便不再接新單。

線程池構造器

在這裏插入圖片描述
ThreadPoolExecutor只有四個構造器,最多有7個參數。其中前5個是必要參數,線程工廠和拒絕策略可選。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

在這裏插入圖片描述

線程添加策略

  1. 如果線程數小於corePoolSize,即使其他工作線程處於空閒狀態,仍會創建新線程運行新任務。【疑問:是一下全建出來,還是一個一個創建】
  2. 如果線程數大於corePoolSize但是少於maxPoolSize,則將任務放入到阻塞隊列中。
  3. 如果隊列已滿,並且線程數小於maxPoolSize,則創建一個新線程運行任務。
  4. 如果隊列已滿,並且線程數大於或等於maxPoolSize,則拒絕該任務。

在這裏插入圖片描述

增減線程的特點

  1. 如果設置相同的corePoolSize和maxThreadPoolSize,相當於創建固定大小的線程池。此時,工作隊列通常選用無界隊列,超時時間爲0L。
  2. 線程池希望保持較少的線程數,只在負載變得很大的時候才增加新的線程。
  3. 通過設置maxPoolSize爲Integer.MAX_VALUE,可以允許線程池容納任意容量的併發任務。
  4. 只有在隊列填滿時,纔會創建多於corePoolSize的新線程。如果採用無界隊列,例如LinkedBlockingQueue,線程數不會超過corePoolSize。

線程池的拒絕策略

拒絕時機–參見execute方法中兩個調用位置

final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}
  1. 當Executor關閉時,提交新任務會被拒絕;
  2. 當Executor對最大線程數和工作隊列容量使用有限邊界,並且已經飽和時。

拒絕策略

  1. AbortPolicy:直接拋出異常,提示沒有提交成功
  2. DiscardPolicy:直接丟棄新任務,不拋出異常。
  3. DiscardOldestPolicy:丟棄最老的任務
  4. CallerRunsPolicy:讓提交任務的線程執行。好處是避免了業務損失;提交速度降低(主線程一直提交任務,線程池和工作隊列滿後,主線程開始執行提交的任務,相當於給了線程池一個緩衝的時間)。

源碼分析

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

拒絕策略接口只有一個方法,目的很明確。

4種拒絕策略實現RejectedExecutionHandler接口,代碼實現也很簡單清楚,此處只介紹DiscardOldestPolicy和CallerRunsPolicy兩種策略。

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        // 1. 如果線程池是RUNNING狀態,並且線程達到飽和,則丟棄最老的任務,重新執行execute方法
        // 2. 如果線程池非RUNNING狀態,則直接丟棄任務。
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }
    // 1. 如果線程池是RUNNING狀態,並且線程達到飽和,則使用調用者線程執行任務
    // 2. 如果線程池非RUNNING狀態,則直接丟棄任務。
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

我們知道直接調用runnable的run()方法是同步調用,調用者線程也就是調用當前線程池execute方法的那個線程。

工作隊列

工作隊列通用策略有3種。

  • 直接提交隊列:默認爲SynchronousQueue,直接將任務提交給線程,不保持他們。如果不存在空閒的線程,則試圖將任務加入隊列將失敗,會新創建一個線程。通常要求無界
    maximumPoolSizes 。
  • 無界隊列:例如LinkedBlockingQueue,用於突發請求。
  • 有界隊列:例如ArrayBlockingQueue,有利於防止資源耗盡,較難調整隊列大小和最大池大小的值。

線程工廠

public interface ThreadFactory {
    Thread newThread(Runnable r);
}

ThreadFactory只有一個方法,是一個函數式接口。

static class DefaultThreadFactory implements ThreadFactory {
    // 標記線程池的數量
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    // 新線程所在線程組
    private final ThreadGroup group;
    // 標記線程的數量
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    // 線程的命名前綴(所在線程池)
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        // 策略線程組或創建線程工廠的線程所在線程組
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        // 只生產用戶線程
        if (t.isDaemon())
            t.setDaemon(false);
        // 線程的安全級別是5
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

常見的線程池

Executors快捷生成的線程池參數總結。
在這裏插入圖片描述

默認的線程工廠ThreadFactory是Executors.defaultThreadFactory(),拒絕策略是AbortPolicy。

  • SingleThreadPool:內部和FixedThreadPool基本一致,只是線程數不同。
  • CachedThreadPool:創建可緩存線程池。是無界線程池,具有自動回收多餘線程的功能。採用的是同步移交隊列,任務直接給到空閒線程或者創建新線程執行。如果線程空閒(沒有任務執行)60秒,回收線程。
  • ScheduledThreadPool:支持定時及週期性任務執行的線程池。
  • WorkStealingPool是JDK8中新增的一種線程池,是新的線程池類ForkJoinPool的擴展,能夠合理地使用CPU對任務做並行操作,適合耗時的場景,例如遞歸、分而治之,並且不加鎖的場景。

線程池的注意點

避免任務堆積,避免線程數過度增加,排查線程泄漏。線程多時,有可能存在線程泄漏,線程執行完畢但是沒被回收,可能是任務邏輯問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章