七: 深入理解Java之線程池

一、JDK線程池的總體api架構:

img

img

img

二、ThreadPoolExecutor類

public class ThreadPoolExecutor extends AbstractExecutorService {
.......
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial parameters and default rejected execution handler.*/
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial parameters and default thread factory.*/
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
    /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial parameters.*/
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
.......
}

2.1參數講解

  • corePoolSize:核心池的大小,這個參數跟後面講述的線程池的實現原理有非常大的關係。在創建了線程池後,默認情況下,線程池中並沒有任何線程,而是等待有任務到來才創建線程去執行任務,除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個方法的名字就可以看出,是預創建線程的意思,即在沒有任務到來之前就創建corePoolSize個線程或者一個線程。默認情況下,在創建了線程池後,線程池中的線程數爲0,當有任務來之後,就會創建一個線程去執行任務,當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中;
  • maximumPoolSize:線程池最大線程數,這個參數也是一個非常重要的參數,它表示在線程池中最多能創建多少個線程;
  • keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用,直到線程池中的線程數不大於corePoolSize,即當線程池中的線程數大於corePoolSize時,如果一個線程空閒的時間達到keepAliveTime,則會終止,直到線程池中的線程數不超過corePoolSize。但是如果調用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數不大於corePoolSize時,keepAliveTime參數也會起作用,直到線程池中的線程數爲0;
  • unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性:
 //天
TimeUnit.HOURS;             //小時
TimeUnit.MINUTES;           //分鐘
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //納秒
  • workQueue:一保存等待執行的任務的阻塞隊列,當提交一個新的任務到線程池以後, 線程池會根據當前線程池中正在運行着的線程的數量來決定對該任務的處理方式,主要有以下幾種處理方式:

    1. **SynchronousQueue:**這個隊列接收到任務的時候,會直接提交給線程處理,而不保留它,如果所有線程都在工作怎麼辦?那就新建一個線程來處理這個任務!所以爲了保證不出現<線程數達到了maximumPoolSize而不能新建線程>的錯誤,使用這個類型隊列的時候,maximumPoolSize一般指定成Integer.MAX_VALUE,即無限大
    > 
    > 2. **LinkedBlockingQueue:內部以一個鏈式結構(鏈接節點)對其元素進行存儲,如果需要的話這一鏈式結構可以選擇一個上限,如果沒有定義上限將使用Integer.MAX_VALUE作爲上限**這個隊列接收到任務的時候,如果當前線程數小於核心線程數,則新建線程(核心線程)處理任務;如果當前線程數等於核心線程數,則進入隊列等待。由於這個隊列沒有最大值限制,即所有超過核心線程數的任務都將被添加到隊列中,這也就導致了maximumPoolSize的設定失效,因爲總線程數永遠不會超過corePoolSize
    > 
    > 3. **ArrayBlockingQueue:一個有界的阻塞隊列**可以限定隊列的長度,接收到任務的時候,如果沒有達到corePoolSize的值,則新建線程(核心線程)執行任務,如果達到了,則入隊等候,如果隊列已滿,則新建線程(非核心線程)執行任務,又如果總線程數到了maximumPoolSize,並且隊列也滿了,則發生錯誤
    > 
    > 4. **DelayQueue:**隊列內元素必須實現Delayed接口,這就意味着你傳進去的任務必須先實現Delayed接口。這個隊列接收到任務時,首先先入隊,只有達到了指定的延時時間,纔會執行任務
    >
    > 5.PriorityBlockingQueue:一個無界的併發隊列,它使用了和類java.util.PriorityQueue一樣的排序規則,你無法向這個隊列中插入null值,所有插入到PriorityBlockingQueue的元素必須實現java.lang.Comparable接口。因此該隊列中元素的排序就取決於你自己的Comparable實現
    >
    > 6.LinkedTransferQueue:由鏈表構成的無界阻塞隊列
    >
    > 7.LinkedBlockingDeque:由鏈表構成的雙向阻塞隊列
    

ArrayBlockingQueue和PriorityBlockingQueue使用較少,一般使用LinkedBlockingQueue和SynchronousQueue。線程池的排隊策略與BlockingQueue有關。

  • threadFactory:線程工廠,主要用來創建線程;
  • handler:它是RejectedExecutionHandler類型的變量,表示線程池的飽和策略。如果阻塞隊列滿了並且沒有空閒的線程,這時如果繼續提交任務,就需要採取一種策略處理該任務。線程池提供了4種策略:
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。 
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。 
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務

2.2AbstractExecutorService的實現:

public abstract class AbstractExecutorService implements ExecutorService {
 
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
    public Future<?> submit(Runnable task) {};
    public <T> Future<T> submit(Runnable task, T result) { };
    public <T> Future<T> submit(Callable<T> task) { };
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                            boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
    };
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
    };
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
    };
}

2.3ExecutorService接口的實現:

public interface ExecutorService extends Executor {
 
    void shutdown();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
 
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

2.4Executor接口的實現

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

到這裏,我們就應該明白了ThreadPoolExecutor類、AbstractExecutorService類、ExecutorService接口和Executor接口幾個之間的關係了。

Executor是最頂層接口,在它裏面只聲明瞭一個方法execute(Runnable),返回值爲void,參數爲Runnable類型,從字面意思可以理解,就是用來執行傳進去的任務的;

ExecutorService接口繼承了Executor接口,並聲明瞭一些方法:submit、invokeAll、invokeAny以及shutDown等;

AbstractExecutorService抽象類實現了ExecutorService接口,基本實現了ExecutorService接口中聲明的所有方法;

ThreadPoolExecutor類繼承了抽象類AbstractExecutorService

在ThreadPoolExecutor類中有幾個非常重要的方法:

    public void execute(Runnable command) {...........}
    public void shutdown() {........}
    public List<Runnable> shutdownNow() {..............}

繼承AbstractExecutorService抽象類中的方法

public Future<?> submit(Runnable task) {
  if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

**execute()**方法實際上是Executor中聲明的方法,在ThreadPoolExecutor進行了具體的實現,這個方法是ThreadPoolExecutor的核心方法,通過這個方法可以向線程池提交一個任務,交由線程池去執行。

**submit()**方法是在ExecutorService中聲明的方法,在AbstractExecutorService就已經有了具體的實現,在ThreadPoolExecutor中並沒有對其進行重寫,這個方法也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果,去看submit()方法的實現,會發現它實際上還是調用的execute()方法,只不過它利用了Future來獲取任務執行結果。

shutdown()和shutdownNow()是用來關閉線程池的。

三、深入剖析線程池實現原理

3.1.線程池狀態

在ThreadPoolExecutor中定義了一個volatile變量,另外定義了幾個static final變量表示線程池的各個狀態:

volatile int runState; //runState表示當前線程池的狀態,它是一個volatile變量用來保證線程之間的可見性
static final int RUNNING    = 0; //當創建線程池後,初始時,線程池處於RUNNING狀態;
static final int SHUTDOWN   = 1; //如果調用了shutdown()方法,則線程池處於SHUTDOWN狀態,此時線程池不能夠接受新的任務,它會等待所有任務執行完畢;
static final int STOP       = 2; //如果調用了shutdownNow()方法,則線程池處於STOP狀態,此時線程池不能接受新的任務,並且會去嘗試終止正在執行的任務;
static final int TERMINATED = 3; //當線程池處於SHUTDOWN或STOP狀態,並且所有工作線程已經銷燬,任務緩存隊列已經清空或執行結束後,線程池被設置爲TERMINATED狀態。

線程池共有五種狀態:

thread-pool-executor-status.jpg

狀態 含義
RUNNING 運行狀態,該狀態下線程池可以接受新的任務,也可以處理阻塞隊列中的任務 執行 shutdown 方法可進入 SHUTDOWN 狀態 執行 shutdownNow 方法可進入 STOP 狀態
SHUTDOWN 待關閉狀態,不再接受新的任務,繼續處理阻塞隊列中的任務 當阻塞隊列中的任務爲空,並且工作線程數爲0時,進入 TIDYING 狀態
STOP 停止狀態,不接收新任務,也不處理阻塞隊列中的任務,並且會嘗試結束執行中的任務 當工作線程數爲0時,進入 TIDYING 狀態
TIDYING 整理狀態,此時任務都已經執行完畢,並且也沒有工作線程 執行 terminated 方法後進入 TERMINATED 狀態
TERMINATED 終止狀態,此時線程池完全終止了,並完成了所有資源的釋放

3.2任務的執行

在瞭解將任務提交給線程池到任務執行完畢整個過程之前,我們先來看一下ThreadPoolExecutor類中其他的一些比較重要成員變量:

private final BlockingQueue<Runnable> workQueue;              //任務緩存隊列,用來存放等待執行的任務
private final ReentrantLock mainLock = new ReentrantLock();   //線程池的主要狀態鎖,對線程池狀態(比如線程池大小、runState等)的改變都要使用這個鎖
private final HashSet<Worker> workers = new HashSet<Worker>();  //用來存放工作集
private volatile long  keepAliveTime;    //線程存貨時間   
private volatile boolean allowCoreThreadTimeOut;   //是否允許爲核心線程設置存活時間
private volatile int   corePoolSize;     //核心池的大小(即線程池中的線程數目大於這個參數時,提交的任務會被放進任務緩存隊列)
private volatile int   maximumPoolSize;   //線程池最大能容忍的線程數
private volatile int   poolSize;       //線程池中當前的線程數
private volatile RejectedExecutionHandler handler; //任務拒絕策略
private volatile ThreadFactory threadFactory;   //線程工廠,用來創建線程
private int largestPoolSize;   //用來記錄線程池中曾經出現過的最大線程數
private long completedTaskCount;   //用來記錄已經執行完畢的任務個數

3.3**.線程池中的線程初始化**

默認情況下,創建線程池之後,線程池中是沒有線程的,需要提交任務之後纔會創建線程。

在實際中如果需要線程池創建之後立即創建線程,可以通過以下兩個方法辦到:

  • prestartCoreThread():初始化一個核心線程;
  • prestartAllCoreThreads():初始化所有核心線程;

3.4**.任務緩存隊列及排隊策略**

在前面我們多次提到了任務緩存隊列,即workQueue,它用來存放等待執行的任務。

workQueue的類型爲BlockingQueue<Runnable>,通常可以取下面三種類型:

1)ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小;

2)LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默認爲Integer.MAX_VALUE;

3)synchronousQueue:這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個線程來執行新來的任務。

3.5任務拒絕策略

當線程池的任務緩存隊列已滿並且線程池中的線程數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略,通常有以下四種策略:

ThreadPoolExecutor.AbortPolicy //丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy //也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy //丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy //由調用線程處理該任務

3.6線程池的關閉

ThreadPoolExecutor提供了兩個方法,用於線程池的關閉,分別是shutdown()和shutdownNow(),其中:

  • shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務
  • shutdownNow():立即終止線程池,並嘗試打斷正在執行的任務,並且清空任務緩存隊列,返回尚未執行的任務

3.7線程池容量的動態調整

ThreadPoolExecutor提供了動態調整線程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

  • setCorePoolSize:設置核心池大小
  • setMaximumPoolSize:設置線程池最大能創建的線程數目大小

當上述參數從小變大時,ThreadPoolExecutor進行線程賦值,還可能立即創建新的線程來執行任務。

pool.awaitTermination(1, TimeUnit.SECONDS) 會每隔一秒鐘檢查一次是否執行完畢(狀態爲 TERMINATED

3.8線程池的工作流程

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-afouIVfD-1574218109059)(C:\Users\zhangruilin\AppData\Roaming\Typora\typora-user-images\1555913855312.png)]

3.9合理配置線程池

  • CPU 密集型
    • CPU 密集的意思是該任務需要大量的運算,而沒有阻塞,CPU 一直全速運行。
    • CPU 密集型任務儘可能的少的線程數量,一般爲 CPU 核數 + 1 個線程的線程池。
  • IO 密集型
    • 由於 IO 密集型任務線程並不是一直在執行任務,可以多分配一點線程數,如 CPU * 2 。
    • 也可以使用公式:CPU 核數 / (1 - 阻塞係數);其中阻塞係數在 0.8 ~ 0.9 之間。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章