線程池之ThreadPoolExecutor

線程池之ThreadPoolExecutor

線程池的工做主要是控制運行的線程的數量,處理過程當中將任務放入隊列,而後在線程建立後啓動這些任務,若是線程數量超過了最大數量,那麼超出數量的線程排隊等候,等其餘線程執行完畢再從隊列中取出任務來執行。java

在開發過程當中,合理地使用線程池可以帶來3個好處:數組

  • 下降資源消耗。經過重複利用已建立的線程下降線程建立和銷燬形成的消耗;
  • 提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能當即執行;
  • 提升線程的可管理性。線程是稀缺資源,若是無限制地建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一分配、調優和監控。

1. 線程池實現原理

線程池主要處理流程:框架

image-20200820163743196

ThreadPoolExecutor執行execute()方法的示意圖:ide

image-20200820164332158

2. 線程池的使用

2.1 線程池建立

經過ThreadPoolExecutor來建立線程池:性能

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

建立線程池的參數:ui

1)corePoolSize:線程池的核心線程數,定義了最小能夠同時運行的線程數量。線程

2)maximumPoolSize:線程池的最大線程數。方隊列中存放的任務達到隊列容量時,房前能夠同時運行的線程數量變爲最大線程數。code

3)keepAliveTime:當線程池中的線程數量大於corePoolSize時,若是沒有新任務提交,核心線程外的線程不會當即銷燬,而是會等待,直到等待的時間超過了KeepAliveTime纔會被回收銷燬。orm

4)unit:keepAliveTime參數的時間單位,包括DAYS、HOURS、MINUTES、MILLISECONDS等。對象

5)workQueue:用於保存等待執行任務的阻塞隊列。能夠選擇如下集個阻塞隊列:

  • ArrayBlockingQueue:是一個基於數組結構的阻塞隊列,此隊列按FIFO原則對元素進行排序;
  • LinkedBlockingQueue:是一個基於鏈表結構的阻塞隊列,此隊列按FIFO排序元素,吞吐量一般高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
  • SynchronousQueue:一個不存儲元素的阻塞隊列。每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量常高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool()使用了這個隊列。
  • PriorityBlockingQueue:一個具備優先級的無限阻塞隊列。

6)threadFactory:用於設置建立線程的工廠,能夠經過工廠給每一個創造出來的線程設置更有意義的名字。使用開源框架guava提供的ThreadFactoryBuilder能夠快速給線程池裏的線程設置有意義的名字:

new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();

7)handler:飽和策略。若當前同時運行的線程數量達到最大線程數量而且隊列已經被放滿,ThreadPoolExecutor定義了一些飽和策略:

  • ThreadPoolExecutor.AbortPolicy:直接拋出RejectedExecutionException異常來拒絕處理新任務;
  • ThreadPoolExecutor.CallerRunsPolicy:只用調用者所在的線程來運行任務,會下降新任務的提交速度,影響程序的總體性能。
  • ThreadPoolExecutor.DiscardPolicy:不處理新任務,直接丟棄掉。
  • ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列中最近的一個任務,執行當前任務。

2.2 向線程池提交任務

execute()方法用於像線程池提交不須要返回值的任務,因此沒法判斷任務是否被線程池執行成功。

executor.execute(new Runnable() {
    @Override
    public void run() {
        // TODO
    }
});

submit()方法用於提交須要返回值的任務。線程池會返回一個future類型的對象,經過這個future對象能夠判斷任務是否執行成功,而且能夠經過future的get()方法獲取返回值,get()方法會阻塞當前線程直到任務完成,而使用get(long timeout, TimeUnit unit)方法則會阻塞當前線程一段時間後當即返回,這時有可能任務尚未執行完。

Future<T> future = executor.submit(hasReturnValueTask);
try {
    T s = future.get();
} catch (InterruptedExecption | ExecutortionExcception e) {
    // 處理異常
    e.printStackTrace();
} finally {
    // 關閉線程池
    executor.shutdown();
}

2.3 關閉線程池

可使用線程池的shutdown或shutdownNow方法來關閉線程池。其原理在於遍歷線程池中的工做線程,而後逐個調用線程的interrupt方法來中斷線程,因此沒法響應中斷的任務可能沒法終止。

兩者區別在於:shutdownNow方法首先將線程池狀態設置爲STOP,而後嘗試中止全部正在執行或暫停任務的線程,並返回等到執行任務的列表,而shutdown只是將線程池的狀態設置爲SHUTDOWN狀態,而後中斷全部沒有整在執行任務的線程。

2.4 合理配置線程池

查看當前設備的CPU核數:

Runtime.getRuntime().availableProcessors()
  • CPU密集型任務:任務須要大量的運算,而沒有阻塞,CPU一直全速運行。

    CPU密集型任務配置儘量的少的線程數量。

    公式:CPU核數 + 1個線程的線程池。

  • IO密集型任務:任務須要大量的IO,即大量的阻塞。

    因爲IO密集型任務線程並非一直在執行任務,能夠多分配一點線程數,如CPU核數*2

    公式:CPU核數/(1-阻塞係數),其中阻塞係數在0.8-0.9之間。

相關文章
相關標籤/搜索