逐漸深入Java多線程(一)----Java線程池和Executor框架說明

目錄

 

從Executor說起

ThreadPoolExecutor

ThreadPoolExecutor的Worker

ThreadPoolExecutor新增任務時的處理流程

ThreadPoolExecutor的線程池狀態

Executors類

1,單線程處理的線程池,newSingleThreadExecutor

2,控制最大併發的線程池,newFixedThreadPool

3,可回收緩存線程池,newCachedThreadPool

4,延時或週期執行任務的線程池,newScheduledThreadPool

5,支持定時任務的單線程線程池,newSingleThreadScheduledExecutor

6,工作竊取式線程池,newWorkStealingPool


從Executor說起

從JDK1.5開始,爲了處理多線程和線程池的問題,java引入了Executor框架。線程池的使用可以減少線程不斷創建和撤銷造成的資源損失。

在java的多線程框架中,有工作單元和執行機制的概念,工作單元指的是Runnable和Callable接口,代表任務本身,執行機制指的是Executor框架,負責任務的處理機制。

 

Executor接口是Executor框架中頂層的接口,只有一個方法:

void execute(Runnable command);

方法的功能是把任務添加到線程中並開始執行。

 

Executor框架的類關係圖:

ExecutorService是Executor接口的子接口,提供了很多用於處理線程的方法,可以用來管理線程的生命週期,比如:

1,void shutdown();

關閉連接池,連接池中已有的任務會繼續執行,但是不能再向連接池中添加新的任務了。

注:當調用這個接口後,線程池就會變成關閉狀態,調用isShutdown()方法返回的會是true,即使線程池中還有剩餘任務正在執行。

2,List<Runnable> shutdownNow();

嘗試中斷當前正在執行的任務,中止等待狀態的任務,並且返回還沒開始執行的任務的列表。

3,boolean isShutdown();

判斷連接池是否已經被關閉。

注:這個方法返回true時不代表線程池中的任務都已經執行完成。

4,boolean isTerminated();

判斷連接池關閉後,線程池中的任務是否都已經執行完畢。

注:此方法應該在線程池關閉後使用,如果線程池沒關閉,此方法永遠返回false。

5,boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

阻塞當前線程,直到線程池關閉且任務都執行完成,或者到達超時時間,或者當前線程被中斷。

注:調用此方法前最好先把線程池關閉。因爲要解除此方法的阻塞,需要線程池關閉而且任務都執行完成,所以,如果調用此方法前沒有把線程池關閉,那基本就沒機會再把線程池關閉了,只能等超時了(或者interrupt)。

6,Future<T> submit(Callable<T> task);

提交一個Callable的任務,並把返回值放入Future中。

調用submit方法後,可以用Future的get()方法獲得Callable的返回值,此時如果任務沒執行完,get()方法會阻塞當前線程。

7,Future<T> submit(Runnable task)

提交一個Runnable的任務。

Runnable的run()方法沒有返回值,但是我們依然可以得到Future返回值,和submit(Callable)不同,submit(Runnable)後調用Future的get()方法不會阻塞當前線程,當然也不會有值,一直都是null,不過我們可以通過Future的isDone()方法來判斷線程是否執行完成。

8,Future<T> submit(Runnable task, T result);

提交一個Runnable的任務。和上面方法的不同就是多了一個T result,這個result會在任務執行完成後被放入Future,然後調用Future的get()方法就可以得到此值,另外,在此場景下的get()方法會阻塞線程。

9,List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;

提交多個任務,並按照連接池的設定開始執行,任務的返回值放入List對應位置的Future中,任務完成前方法會阻塞。

10,List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                              long timeout, TimeUnit unit)
    throws InterruptedException;

提交多個任務,並設置任務的超時時間,任務的返回值放入List對應位置的Future中,任務完成前方法會阻塞。

注:此處的超時時間是針對整個連接池的,不是針對單個任務的,也就是說,從連接池開始執行任務,到超時時間爲止,沒完成和沒開始執行的任務都會被中止。

11,<T> T invokeAny(Collection<? extends Callable<T>> tasks)
    throws InterruptedException, ExecutionException;

提交多個任務,在完成第一個任務後,返回結果,並把其他任務用interrupt()方法全部中斷。

注:線程運行過程中因爲發生異常而結束的,不算完成任務,如果所有線程都因發生異常而結束,則方法返回最後一個異常。

12,<T> T invokeAny(Collection<? extends Callable<T>> tasks,
                long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException;

和前面的invokeAny()方法相同,不過增加了超時的設定。

 

ThreadPoolExecutor

ThreadPoolExecutor是創建並處理線程池的類,儘量使用這個類來創建線程池,自己定義合適的線程池參數,避免直接用Executor接口和Executors工具類,他們會導致無限長的任務隊列(比如newSingleThreadExecutor),或者導致無限多的線程(比如newCachedThreadPool),存在內存溢出的風險。

 

ThreadPoolExecutor類的構造:

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.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

構造方法的幾個參數說明如下:

1,int corePoolSize

線程池中核心線程數。活動線程未達到此值時,新的任務會新建Worker線程來處理。如果要執行的任務多於此值,多出來的任務會被放進workQueue任務隊列或擴展線程上限至maximumPoolSize後繼續新建Worker線程來處理。

注:當corePoolSize未滿時,新加的任務一定會分配新的線程來處理,即使有Worker空閒,所以newCachedThreadPool把這個值設爲0,使得新加的任務優先考慮空閒Worker,沒有空閒Worker纔會新建線程。

2,int maximumPoolSize

線程池中的最大線程數。

3,long keepAliveTime

空閒線程銷燬時間。當線程池中線程數大於corePoolSize時,多出來的線程會在此時間後被銷燬。

4,TimeUnit unit

線程銷燬時間的時間單位。TimeUnit是個枚舉,提供的時間單位有:天,小時,分鐘,秒,毫秒,微秒,納秒。(這個納秒是什麼場合下用的?)

5,BlockingQueue<Runnable> workQueue

任務隊列。當任務數小於corePoolSize時,任務被添加到線程池分配線程來處理,而當corePoolSize已滿時,任務就放在這個隊列裏。

BlockingQueue是在java.util.concurrent包下的一個接口類,這個接口有以下幾個實現類:

  • ArrayBlockingQueue
  • DelayQueue
  • LinkedBlockingDeque
  • LinkedBlockingQueue
  • LinkedTransferQueue
  • PriorityBlockingQueue
  • SynchronousQueue

分別適用於不同的場景。

6,ThreadFactory threadFactory

創建新線程用的工廠類,ThreadFactory接口只有一個方法:

Thread newThread(Runnable r);

另外,這個工廠類有默認實現,可以通過:

Executors.defaultThreadFactory();

來獲取。

7,RejectedExecutionHandler handler

拒絕策略。任務太多來不及處理時的處理策略。

這個參數也有默認值,就是ThreadPoolExecutor類中的靜態變量:

private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();

AbortPolicy是ThreadPoolExecutor類中的一個內部類,代碼如下:

public static class AbortPolicy implements RejectedExecutionHandler {
    /**
     * Creates an {@code AbortPolicy}.
     */
    public AbortPolicy() { }

    /**
     * Always throws RejectedExecutionException.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     * @throws RejectedExecutionException always
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

可見,這個類拒絕新線程的辦法是拋出了一個RejectedExecutionException異常。

 

ThreadPoolExecutor提供了以下幾種不同的拒絕策略:

1,CallerRunsPolicy。在調用者線程中直接執行此任務,前提是線程池還未關閉。

2,DiscardPolicy。開心愉快的把要添加的任務忽略掉。顯然此策略會導致任務丟失。

3,DiscardOldestPolicy。丟棄隊列中最老的任務,也就是最早被添加到隊列裏的那個任務,也就是下一個要執行的任務,然後重試提交任務。

4,AbortPolicy。拋異常。此爲默認策略。

我們也可以自己定義拒絕策略,實現RejectedExecutionHandler接口並重寫

void rejectedExecution(Runnable r, ThreadPoolExecutor executor);

接口即可。

 

ThreadPoolExecutor的Worker

Worker是在ThreadPoolExecutor中定義的一個內部類,用於處理線程池中的任務。

Worker類維護了兩個參數:

一個是要執行的任務firstTask,這個參數起名叫firstTask是有原因的,每個Worker初始化的時候都會同時分配一個任務並賦值給firstTask,就是所謂的第一個任務,而當Worker完成這個任務後又會調用getTask()方法從BlockingQueue中獲取新的任務並執行。

第二個是Worker對象專用的線程,每個Workder對象都有屬於自己的線程。Worker的線程啓動時,同時會調用Worker的run()方法。

可見,Worker實際上對要執行的任務進行了封裝,之所以使用Worker來封裝和處理任務而不是直接調用任務的run()方法,是因爲Worker使用了一種鎖機制,使正在執行中的任務不會輕易被中斷。

當Worker完成當前任務後,會從BlockingQueue中獲取新的任務執行,如果BlockingQueue中沒有任務,則Worker退出。

 

ThreadPoolExecutor新增任務時的處理流程

當執行ThreadPoolExecutor.execute()或者ThreadPoolExecutor.sumit()時,大概的處理邏輯如下:

1,判斷當前Worker數(也就是線程數)是否小於corePoolSize,如果小於則新增Worker並處理該任務。這一步會讓線程數大於0而小於等於corePoolSize。

2,如果當前線程數已經大於corePoolSize,則嘗試把任務放入BlockingQueue。如果放入成功,則在BlockingQueue中等待Worker處理。

3,如果放入BlockingQueue失敗則直接新建Worker並處理該任務,只要線程數沒達到maximumPoolSize。這一步和第一步很像,區別只是這一步的邏輯可能會讓線程數大於corePoolSize而小於maximumPoolSize。另外,只有corePoolSize已滿且無法放入BlockingQueue時纔會來到這一步。

4,如果放入BlockingQueue失敗而且maximumPoolSize已滿,則執行拒絕策略。

 

ThreadPoolExecutor的線程池狀態

ThreadPoolExecutor的線程池有以下幾種狀態:

1,RUNNING。

處於此狀態下的線程池,可接受新任務,可繼續執行池中未完成的任務。

2,SHUTDOWN。

處於此狀態下的線程池,不能接受新任務,但是可以繼續執行池中未完成的任務。

調用shutdown()方法或finalize()方法,會讓線程池進入此狀態。

3,STOP

處於此狀態下的線程池,不能接受新任務,也不會繼續執行池中未完成的任務,當前正在執行的任務會發送中斷指令(Interrupt)。

在RUNNING或者SHUTDOWN狀態下調用shutdownNow()方法,會讓線程池進入此狀態。

4,TIDYING

進入此狀態的連接池會觸發terminated()鉤子,調用terminated()方法。

當線程池中所有任務都完成,而且所有Worker線程都關閉後,線程池會進入此狀態。

5,TERMINATED

當terminated()方法調用結束後,線程池進入此狀態。

 

這些狀態之間的轉換關係如下圖:

圖2

 

Executors類

Executors和Executor的不同,就像Collections和Collection的不同一樣,Executor是接口,而Executors是相關的工具類,提供了很多創建線程池的方法。

比如這個方法:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

作用是創建單一處理線程的線程池。

 

Executors可以創建以下幾種不同的線程池,分別是:

1,單線程處理的線程池,newSingleThreadExecutor

Executor創建單線程處理的線程池,提供了兩個方法:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}

返回的是一個FinalizableDelegatedExecutorService類,這是Executors類中自己定義的一個類,提供了對ThreadPoolExecutor類的一個簡單封裝。

這個ThreadPoolExecutor,corePoolSize爲1,maximumPoolSize爲1,keepAliveTime爲0,隊列爲LinkedBlockingQueue,長度不限。

參照ThreadPoolExecutor提交任務時的處理邏輯可以看出此線程池的特點:只可能生成一個Worker線程來處理任務,但是可以向隊列中無限添加任務,並由這個Worker一個一個處理。

要小心因爲隊列中任務過多而導致的內存溢出。

和直接返回ThreadPoolExecutor相比,FinalizableDelegatedExecutorService類重寫了finalize()方法:

static class FinalizableDelegatedExecutorService
    extends DelegatedExecutorService {
    FinalizableDelegatedExecutorService(ExecutorService executor) {
        super(executor);
    }
    protected void finalize() {
        super.shutdown();
    }
}

finalize()的內容是把線程池關閉,而finalize()方法會被垃圾回收機制調用,也就是說,即使我們自己不調用shutdown()方法,垃圾回收器也會幫我們把線程池關閉。然而,作爲負責任的開發,不能依靠finalize()方法,因爲java從來不保證finalize()方法什麼時候調用,甚至調不調用,所以,請手動關閉連接池。

 

2,控制最大併發的線程池,newFixedThreadPool

Executor創建控制最大併發的線程池,提供了兩個方法:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}

這個ThreadPoolExecutor,corePoolSize爲nThreads,maximumPoolSize爲nThreads,keepAliveTime爲0,隊列爲LinkedBlockingQueue,長度不限。

參照ThreadPoolExecutor提交任務時的處理邏輯可以看出此線程池的特點:最多可以生成nThreads個Worker線程來處理任務,但是可以向隊列中無限添加任務,並由這個Worker一個一個處理。

要小心因爲隊列中任務過多而導致的內存溢出。

 

3,可回收緩存線程池,newCachedThreadPool

Executor的連接池本來就是可回收的,Worker線程完成當前任務後就會從線程池中繼續獲得任務並執行,但是,線程池中的活動線程數小於corePoolSize時會直接新建線程,而不是使用已有線程,所以這個所謂可回收緩存線程池,只不過是把corePoolSize設成了0。

Executor創建可回收緩存線程池,提供了兩個方法:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}

這個ThreadPoolExecutor,corePoolSize爲0,maximumPoolSize爲Integer.MAX_VALUE,keepAliveTime爲60秒,隊列爲SynchronousQueue。

參照ThreadPoolExecutor提交任務時的處理邏輯可以看出此線程池的特點:

1.)因爲corePoolSize爲0,所以不會生成Worker添加到corePoolSize核心線程池中。

2.)因爲任務隊列是SynchronousQueue,所以,如果沒有空閒Worker正在等待時,無法把任務添加到BlockingQueue中,只能直接進行第三步,生成新Worker。因爲maximumPoolSize爲Integer.MAX_VALUE,所以生成的Worker數可以達到Integer.MAX_VALUE這麼多。要小心因爲Worker數過多而導致的內存溢出。

3.)所以當我們向這個連接池添加任務時只可能出現兩種情形:要麼沒有空閒Worker,然後生成新的Worker線程來處理,要麼恰好有空閒Worker,讓空閒Worker來處理,這也就是所謂可回收和緩存的原理。

 

4,延時或週期執行任務的線程池,newScheduledThreadPool

Executor創建定時任務線程池,提供了兩個方法:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}

返回的是一個ScheduledThreadPoolExecutor,參數中的corePoolSize會成爲ScheduledThreadPoolExecutor的corePoolSize。

其中ScheduledThreadPoolExecutor()構造的代碼:

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

ScheduledThreadPoolExecutor的父類就是ThreadPoolExecutor,所以這個線程池返回的實際上也是一個ThreadPoolExecutor,其corePoolSize自定義,maximumPoolSize爲Integer.MAX_VALUE,BlockingQueue爲DelayedWorkQueue。

由參數可以看出這個線程池的特點:

1,核心線程數自定義,最大線程數可以很大。

2,BlockingQueue用的是DelayedWorkQueue,這是以一種有優先級的隊列,基於堆排序,按照任務的延時時間進行排序。

所以我們可以用:

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

方法延時執行任務。

可以用:

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                 long initialDelay,
                                                 long delay,
                                                     TimeUnit unit);

方法不斷循環執行線程池中的任務,此方法設定任務完成後到下次執行的延時時間。

可以用:

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                              long initialDelay,
                                              long period,
                                              TimeUnit unit);

方法循環執行線程池中的任務,此方法設定任務執行開始到下次執行的延時時間,但是如果延時時間到了上次任務還沒執行完,則會等待任務執行完後立即開始。

 

5,支持定時任務的單線程線程池,newSingleThreadScheduledExecutor

Executor支持定時任務的單線程線程池,提供了兩個方法:

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1, threadFactory));
}

二者的區別是一個ThreadFactory參數。

返回的是一個DelegatedScheduledExecutorService,提供了一個對ScheduledThreadPoolExecutor的封裝。

這個線程池和ScheduledThreadPoolExecutor差不多,可以重複執行線程池中的任務,可以配置從任務開始時間開始的延遲,或從任務結束時間開始的延遲,用的方法也一樣,只不過這是一個corePoolSize爲1的線程池,只會有一個線程來處理任務,如果一個任務調用Thread.sleep(),所有的任務都會暫停。

 

6,工作竊取式線程池,newWorkStealingPool

JDK1.8才加入的線程池類型,使用的是ForkJoinPool,ForkJoinPool是JDK1.7加入的類,父類是AbstractExecutorService。這種線程池會創建足夠多個線程來處理任務,充分利用多核CPU的性能,不保證任務執行的順序。

Executor創建newWorkStealingPool,提供了兩個方法:

public static ExecutorService newWorkStealingPool() {
    return new ForkJoinPool
        (Runtime.getRuntime().availableProcessors(),
         ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}
public static ExecutorService newWorkStealingPool(int parallelism) {
    return new ForkJoinPool
        (parallelism,
         ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}

返回的是一個ForkJoinPool。這個線程池的特點也是由ForkJoinPool決定的。

 

以上

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