面試必問的線程池須要瞭解一下

1.爲何要用線程池

線程池提供了一種任務的提交與任務的執行解偶的策略,而且有如下幾種優點java

提升資源利用率
經過複用本身的線程來下降建立線程和銷燬線程的開銷。
若是不用線程池而採用爲每一個任務都建立一個新線程的話會很浪費系統資源。由於建立線程和銷燬線程都是耗系統資源的行爲。除此以外還會因爲線程過多而致使JVM出現OutOfMemory數組

提升響應速度
當新來一個任務時,若是有空閒線程存在可當即執行任務,中間節省了建立線程的過程緩存

統一管理線程
若是不用線程池來管理,而是無限建立線程的話不只消耗系統資源,並且還會致使系統不穩定。使用線程池能夠進行統一分配,調優以及監控。bash

2.常見線程池以及參數

2.1 建立線程池

經過Executors的工廠方法來建立線程池,好比建立一個固定線程數的線程池多線程

ExecutorService executorService = Executors.newFixedThreadPool(5);
複製代碼

經過ThreadPoolExecutor的構造函數來建立線程池ide

ThreadPoolExecutor pool = new ThreadPoolExecutor(int corePoolSize,
                                                 int maxmumPoolSize,
                                                 long keepAliveTime,
                                                 TimeUnit unit,
                                                 BolockingQueue<Runnable> workQueue,
                                                 ThreadFactory factory,
                                                 RejectedExecusionHandler handler);
複製代碼

官方比較推薦使用後者,由於能夠靈活選擇參數配置,以及自定義配置函數

不管是採用那種方式建立一個線程池,它內部都是經過 ThreadPoolExecutor的構造函數來實現的。因此要想全面掌握線程池的各類特性以及性能的話須要對 ThreadPoolExecutor類深刻理解,包括各類參數的意義,參數之間是如何配合工做的等等。性能

2.2 線程池參數
  1. corePoolSize 核心線程數。默認狀況下,當提交一個任務時線程池會新建一個線程池(及時有空閒線程存在),直到線程數量等於基本大小(也能夠預初始化線程)ui

  2. workQueue 一個存聽任務的阻塞隊列,當線程池裏的基本線程都在忙着的時候,提交一個新任務的話會暫時存放到阻塞隊列,等待執行,經常使用的阻塞隊列有一下幾種this

    • ArrayBlockingQueue 基於數組實現的FIFO阻塞隊列(有限長度)
    • LinkedBlockingQueue 基於鏈表實現的FIFO阻塞隊列(無限長度)
    • SynchronousQueue 自己不存儲任務,當有任務來的時候會一直阻塞,直到線程去執行
    • PriorityBlockingQueue 具備優先級的優先隊列
  3. maximumPoolSize 最大線程數。當全部的核心線程都在運行而且阻塞隊列也滿了的話會建立額外的幾個線程來執行,這時候線程池裏的全部線程數量就是最大線程數量。若是線程池採用的是像LinkedBlockingQueue這種無界隊列的話,該參數不會起到做用

  4. KeepAliveTimeTimeUnit 若是某個線程的空閒時間大於KeepAliveTime的時候會被標記爲可回收, 而且當前線程池裏的數量大於核心線程數量的時候會被終止。因此該參數跟maximumPoolSize同樣,使用無界隊列的時候不起做用,TimeUnit是時間單位

  5. RejectedExecusionHandler 飽和策略。若是咱們的任務隊列滿了,而且線程池裏的線程數量已經達到了最大線程數,並且這些線程都再也不空閒狀態。這時候新提交任務的話,沒法去執行,因此須要一種飽和策略來去處理這些任務。Java提供了一下幾種策略

    • AbortPolicy (默認策略) 直接拋異常,調用者須要根據本身的需求去捕獲處理異常
    • DiscardPolicy 直接丟棄,不處理直接丟棄該任務
    • DiscardOldestPolicy 丟棄隊列裏最近的一個任務,並執行當前任務。若是是優先隊列的話會丟棄優先級最高的任務,因此不推薦與優先隊列一塊兒組合使用
    • CallerRunsPolicy 由調用者當前線程來執行該任務。
2.3 常見線程池

Java類庫提供了靈活的建立線程池方法,能夠經過調用Executors中的靜態工廠方法來建立一個線程池。

  1. newFixedThreadPool 將建立一個固定線程數量的線程池,每當提交一個任務時就建立一個線程。直到達到線程池的最大數量。corePoolSize和maximumPoolSize值相同,而且採用了LinkedBlockingQueue,因此最大線程池數,飽和策略,存活時間等等參數都將不被用到。
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
複製代碼
  1. newSingleThreadExecutor 一個但線程的Executor,它跟上面的newFixedThreadPool相似,區別在於只有一個工做線程。經過該線程池能夠確保有序的執行隊列中的任務,由於只有一個工做線程,因此出隊的順序就是執行的順序。
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
複製代碼
  1. newCachedThreadPool 此線程池的線程數量沒有顯式的限制默認爲Integer.MAX_VALUE,能夠爲每一個任務建立一個線程來處理,可是它沒這麼作,它是具備緩存線程的功能,是一種可伸縮的。好比,當處理新任務是首先看有沒有空閒可用的線程來處理,若是沒有的話纔會新建立。而且當建立的線程空閒一段時間以後會回收(默認一分鐘)
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
複製代碼
  1. newScheduledThreadPool
    建立一個固定線程數的線程池,而且以延遲或定時的方式來執行。
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
複製代碼

3.執行流程

線程池執行任務是由Executor接口的execute()方法來執行的,下面看下執行任務流程的一段核心代碼(java8)

public void execute(Runnable command) {

        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)首先,若是當前工做的線程數量小於核心線程數,則新建立一個線程來執行。若是核心線程池數滿了,

2)判斷線程池的任務隊列是否已滿,若是沒滿的話把該任務放到任務隊列裏,等待覈心線程空閒以後去執行,若是滿了的話進入下一階段

3)判斷判斷線程池裏工做的線程數量是否達到了最大線程數,若是沒達到則建立一個新的線程來去執行。不然,採用飽和策略來處理

若是進去看內部實現的話會發現,線程池會把每一個工做線程包裝成Worker,而把須要執行的任務包裝成Task來運行。

4.健康檢查

在項目中使用線程池的時候有必要對線程池進行監控,這樣能夠根據線程池的使用情況快速定位問題。能夠經過線程池提供的參數進行監控,在監控線程池的時候可使用如下屬性。

  • taskCount 線程池須要執行的任務數量
  • completedTaskCount 已經成功運行的任務數
  • largestPoolSize 曾經出現的最多線程數
  • getPoolSize 獲取線程數
  • getActiveCount 活動線程數

示例代碼

public class Task  implements Runnable{
    private int i;

    public Task(int i) {
        this.i = i;
    }

    @Override
    public void run() {
        System.out.println("task num ="+i);
    }
}
複製代碼
public class Test {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(10,20,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
        for (int i=0;i<5;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("完成任務數= "+pool.getCompletedTaskCount()+" 任務數= "+pool.getTaskCount()+" " +
                    ""+" 活動線程數="+pool.getActiveCount() +"" +" 線程數="+pool.getPoolSize() +" "+" 最大線程數="+pool.getLargestPoolSize() );

            Task task = new Task(i);
            pool.execute(task);
        }
        pool.shutdown();

    }
}
複製代碼

打印結果以下:

完成任務數= 0  任務數= 0   活動線程數=0  線程數=0    最大線程數=0
task num =0
完成任務數= 1  任務數= 1   活動線程數=0  線程數=1    最大線程數=1
task num =1
完成任務數= 2  任務數= 2   活動線程數=0  線程數=2    最大線程數=2
task num =2
完成任務數= 3  任務數= 3   活動線程數=0  線程數=3    最大線程數=3
task num =3
完成任務數= 4  任務數= 4   活動線程數=0  線程數=4    最大線程數=4
task num =4
複製代碼
相關文章
相關標籤/搜索