1. 程式人生 > >【轉】執行緒池和Executor框架

【轉】執行緒池和Executor框架

æ¬èæ维导å¾

 

一 使用執行緒池的好處

二 Executor 框架

2.1 簡介

2.2 Executor框架結構(主要由三部分構成) 

2.3 Executor框架使用說明示意圖

三 ThreadPoolExecutor詳解

3.1 ThreadPoolExecutor類中重要的屬性

3.2 ThreadPoolExecutor的構造方法

3.3 如何建立ThreadPoolExecutor

3.4 FixedThreadPool詳解

3.5 SingleThreadExecutor詳解

3.6 CachedThreadPool詳解

3.7 ThreadPoolExecutor使用示例

3.7.1 示例程式碼

3.7.2 shutdown() vs shutdowNow()

3.7.3 isShutdown() vs isTerminated()

四 ScheduledThreadPoolExecutor詳解

4.1 簡介

4.2 ScheduledThreadPoolExecutor執行機制 

4.3 ScheduledThreadPoolExecutor執行週期任務的步驟

4.4 ScheduledThreadPoolExecutor使用示例

4.4.1 ScheduledExecutorService scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)方法

4.4.2 ScheduledExecutorService scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)方法

4.4.3 scheduleWithFixedDelay() vs scheduleAtFixedRate()

五 各種執行緒池的適用場景介紹


一 使用執行緒池的好處

執行緒池提供了一種限制和管理資源。每個執行緒池還維護了一些基本資訊,如已完成任務的執行緒數量

這裡借用《Java併發程式設計的藝術》提到的來說一下使用執行緒池的好處:

降低資源消耗。

通過重複利用已建立的執行緒減少建立和銷燬執行緒的消耗
提高響應速度。當任務到達時候,不需要等待執行緒建立就能執行。
提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控。

二 Executor 框架

2.1 簡介

Executor 框架是Java5之後引進的,在Java 5之後,通過 Executor 來啟動執行緒比使用 Thread 的 start 方法更好,除了更易管理,效率更好(用執行緒池實現,節約開銷)外,還有關鍵的一點:有助於避免 this 逃逸問題。

補充:this逃逸是指在建構函式返回之前其他執行緒就持有該物件的引用. 呼叫尚未構造完全的物件的方法可能引發令人疑惑的錯誤。

2.2 Executor框架結構(主要由三部分構成) 

 1. 任務

執行任務需要實現的Runnable介面Callable介面
Runnable介面Callable介面實現類都可以被ThreadPoolExecutorScheduledThreadPoolExecutor執行。

兩者的區別:

Runnable介面不會返回結果但是Callable介面可以返回結果。後面介紹Executors類的一些方法的時候會介紹到兩者的相互轉換。

 2. 任務執行

任務執行的核心介面是Executor介面,以及繼承這個介面的子介面ExecutorService 

ScheduledThreadPoolExecutorThreadPoolExecutor這兩個關鍵類實現了ExecutorService介面

ScheduledThreadPoolExecutor類描述:

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService {

注意: ScheduledThreadPoolExecutor類繼承ThreadPoolExecutor, ThreadPoolExecutor則繼承了AbstractExecutorService類,AbstractExecutorService類則是實現ExecutorService介面。

ScheduledThreadPoolExecutor並且實現了介面ScheduledExecutorService,這個介面也是繼承了ExecutorService介面,見下圖所示

ThreadPoolExecutor類描述:

public class ThreadPoolExecutor extends AbstractExecutorService {
public abstract class AbstractExecutorService implements ExecutorService {

ä»»å¡çæ§è¡ç¸å³æ¥å£

3 非同步計算的結果 

Future介面以及Future介面的實現類FutureTask類。
當我們把Runnable介面或Callable介面的實現類提交(呼叫submit方法)給ThreadPoolExecutor或ScheduledThreadPoolExecutor時,會返回一個FutureTask物件,FutureTask物件能夠獲取到執行緒的執行結果。
 以抽象類AbstractExecutorService的submit方法為例說明

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

2.3 Executor框架使用說明示意圖

Executor æ¡æ¶ç使ç¨ç¤ºæå¾

1. 主執行緒首先要建立實現Runnable或者Callable介面的任務物件。
備註: 工具類Executors可以實現Runnable物件和Callable物件之間的相互轉換。(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule))。

2. 然後可以把建立完成的Runnable物件直接交給ExecutorService執行(ExecutorService.execute(Runnable command));或者也可以把Runnable物件或Callable物件提交給ExecutorService執行(ExecutorService.submit(Runnable task)或ExecutorService.submit(Callable task))。

執行execute()方法和submit()方法的區別是什麼呢?
1)execute()方法用於提交不需要返回值的任務,所以無法判斷任務是否被執行緒池執行成功與否;
2)submit()方法用於提交需要返回值的任務。執行緒池會返回一個future型別的物件,通過這個future物件可以判斷任務是否執行成功,並且可以通過future的get()方法來獲取返回值,get()方法會阻塞當前執行緒直到任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞當前執行緒一段時間後立即返回,這時候有可能任務沒有執行完。

3. 如果執行ExecutorService.submit(…),ExecutorService將返回一個實現Future介面的物件(我們剛剛也提到過了執行execute()方法和submit()方法的區別,到目前為止的JDK中,返回的是FutureTask物件)。由於FutureTask實現了Runnable,程式設計師也可以建立FutureTask,然後直接交給ExecutorService執行。

4. 最後,主執行緒可以執行FutureTask.get()方法來等待任務執行完成。主執行緒也可以執行FutureTask.cancel(boolean mayInterruptIfRunning)來取消此任務的執行。

三 ThreadPoolExecutor詳解

3.1 ThreadPoolExecutor類中重要的屬性

ThreadPoolExecutoræ¯è¾éè¦çå个å±æ§

3.2 ThreadPoolExecutor的構造方法

看下基本的構造器方法,其他的構造器方法都是會呼叫這個構造器方法

    /**
     * 用給定的初始引數建立一個新的ThreadPoolExecutor。

     * @param keepAliveTime 當執行緒池中的執行緒數量大於corePoolSize的時候,如果這時沒有新的任務提交,
     *核心執行緒外的執行緒不會立即銷燬,而是會等待,直到等待的時間超過了keepAliveTime;
     * @param unit  keepAliveTime引數的時間單位
     * @param workQueue 等待佇列,當任務提交時,如果執行緒池中的執行緒數量大於等於corePoolSize的時候,把該任務封裝成一個Worker物件放入等待佇列;
     * 
     * @param threadFactory 執行者建立新執行緒時使用的工廠
     * @param handler RejectedExecutionHandler型別的變數,表示執行緒池的飽和策略。
     * 如果阻塞佇列滿了並且沒有空閒的執行緒,這時如果繼續提交任務,就需要採取一種策略處理該任務。
     * 執行緒池提供了4種策略:
        1.AbortPolicy:直接丟擲異常,這是預設策略;
        2.CallerRunsPolicy:用呼叫者所在的執行緒來執行任務;
        3.DiscardOldestPolicy:丟棄阻塞佇列中靠最前的任務,並執行當前任務;
        4.DiscardPolicy:直接丟棄任務;
     */
    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;
    }

3.3 如何建立ThreadPoolExecutor

在《阿里巴巴Java開發手冊》"併發處理"這一章中,指出執行緒必須由執行緒池提供,不允許在應用中顯示建立執行緒。

為什麼?

執行緒池具有減少系統消耗和提高響應時間的優點,不使用執行緒池可能出現系統建立大量同類執行緒消耗記憶體或者上下文切換的問題

另外《阿里巴巴Java開發手冊》中強制執行緒池不允許使用 工具類Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險

Executors 返回執行緒池物件的弊端如下:

FixedThreadPool 和 SingleThreadExecutor : 允許請求的佇列的長度是Integer.MAX_VALUE,可能堆積大量的請求,導致OOM。
CachedThreadPool 和 ScheduledThreadPool : 允許建立的執行緒數量為 Integer.MAX_VALUE ,可能會建立大量執行緒,從而導致OOM。

建立ThreadPoolExecutor方式1

éè¿æé æ¹æ³å®ç°

建立ThreadPoolExecutor方式2

這種使用Executors工具類建立的方式,不建議使用,主要看下幾種型別執行緒池的構造方式

  • FixedThreadPool 
  • SingleThreadExecutor
  • CachedThreadPool

éè¿Executor æ¡æ¶çå·¥å·ç±»Executorsæ¥å®ç°

3.4 FixedThreadPool詳解

FixedThreadPool可重用固定執行緒數量的執行緒池

   /**
     * 建立一個可重用固定數量執行緒的執行緒池
	 *在任何時候至多有n個執行緒處於活動狀態
	 *如果在所有執行緒處於活動狀態時提交其他任務,則它們將在佇列中等待,
	 *直到執行緒可用。 如果任何執行緒在關閉之前的執行期間由於失敗而終止,
	 *如果需要執行後續任務,則一個新的執行緒將取代它。池中的執行緒將一直存在
	 *知道呼叫shutdown方法
     * @param nThreads 執行緒池中的執行緒數
     * @param threadFactory 建立新執行緒時使用的factory
     * @return 新建立的執行緒池
     * @throws NullPointerException 如果threadFactory為null
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }

另外還有一個FixedThreadPool的實現方法,和上面類似,不在贅述

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

從上面原始碼看出FixedThreadPool的corePoolSize和maximumPoolSize都被設定為nThreads。

FixedThreadPool的execute()方法執行示意圖(該圖片來源:《Java併發程式設計的藝術》):

FixedThreadPool的execute()方法執行示意圖

執行過程說明

1. 執行緒池中的執行緒數量小於corepoolSize,每個到達的任務建立一個執行緒去執行

2. 執行緒池中的執行緒數量等於corePoolSize時候,到達的請求將會放入到佇列中

3. 當有執行緒空閒,從佇列中取出任務執行

FixedThreadPool使用無界佇列 LinkedBlockingQueue(佇列的容量為Intger.MAX_VALUE)作為執行緒池的工作佇列會對執行緒池帶來如下影響:

1. 當執行緒數目達到corePoolSize,因為使用無界佇列,最大執行緒數量永遠不會超過corePoolSize

2. 因為1,maximumPoolSize引數無效

3. 因為執行緒數量永遠不會超過corePoolSize, 引數keepalivetime無效

4. 執行中的FixedThreadPool不會拒絕任務,會導致請求堆積,可能出現OOM問題。

3.5 SingleThreadExecutor詳解

SingleThreadExecutor是使用單個執行緒的Executor,下面看下SingleThreadExecutor的實現

   /**
     *建立使用單個worker執行緒執行無界佇列的Executor
	 *並使用提供的ThreadFactory在需要時建立新執行緒
     * @param threadFactory 建立新執行緒時使用的factory
     * @return 新建立的單執行緒Executor
     * @throws NullPointerException 如果ThreadFactory為空
     */
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
   public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

從原始碼可以看出,SingleThreadExecutor的corePoolsize和axnumPoolSize都設定為1,佇列使用的是無界佇列(容量為Integer.MAX_VALUE),造成的影響和FixedThreadExecutor一樣

SingleThreadExecutor的執行示意圖(該圖片來源:《Java併發程式設計的藝術》):
SingleThreadExecutor的執行示意圖

上圖說明

1. 任務到達,執行緒池中的執行緒數量為0,建立一個執行緒執行,並且執行緒池中只能建立這一個執行緒。

2. 當多個任務到達,執行緒忙碌,將任務加入到佇列中

3. 執行緒空閒下來,從佇列中取任務執行。

3.6 CachedThreadPool詳解

CachedThreadPool是一個根據需要建立新執行緒的執行緒池,實現如下:

    /**
     * 建立一個執行緒池,使用ThreadFactory建立執行緒,並且在需要的情況下建立新的執行緒,否則使用之前建立的執行緒處理任務
     * @param threadFactory 執行緒建立工廠
     * @return 
     * @throws 當threadFactory為null,丟擲NullPointerException
     */
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

從原始碼看,corePoolSize為0,maxnumPoolSize設定為Integer.MAX_VALUE,當不斷有新的任務加入,並且沒有執行緒空閒的情況下,就會不斷建立新的執行緒,這種情況會大量消耗系統資源,會導致OOM問題。

achedThreadPool的execute()方法的執行示意圖(該圖片來源:《Java併發程式設計的藝術》):
CachedThreadPool的execute()方法的執行示意圖

上圖說明:

 1. 因為CachedThreadPool的corePoolSize設定為0,當有任務加入,會被新增到佇列中,當已建立的執行緒中有空閒的,

會從佇列中poll任務執行

2. 當corePoolSize為0或者執行緒池中沒有執行緒空閒,就會建立新的執行緒執行任務。

3.7 ThreadPoolExecutor使用示例

3.7.1 示例程式碼

建立一個Runnable實現類(或者建立Callable介面的實現類,兩者的區別在於:Runnable不會返回執行結果,Callable會返回執行結果,兩者可以通過類Executors的方法進行轉換)

import java.util.Date;

/**
 * 這是一個簡單的Runnable類,需要大約5秒鐘來執行其任務。
 */
public class WorkerThread implements Runnable {

    private String command;

    public WorkerThread(String s) {
        this.command = s;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
        processCommand();
        System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
    }

    private void processCommand() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return this.command;
    }
}

編寫測試程式,以FixedThreadPool為例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExecutorDemo {

    public static void main(String[] args) {
        //建立一個FixedThreadPool物件
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            //建立WorkerThread物件(WorkerThread類實現了Runnable 介面)
            Runnable worker = new WorkerThread("" + i);
            //執行Runnable
            executor.execute(worker);
        }
        //終止執行緒池
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("Finished all threads");
    }
}

輸出示例:

pool-1-thread-5 Start. Time = Thu May 31 10:22:52 CST 2018
pool-1-thread-3 Start. Time = Thu May 31 10:22:52 CST 2018
pool-1-thread-2 Start. Time = Thu May 31 10:22:52 CST 2018
pool-1-thread-4 Start. Time = Thu May 31 10:22:52 CST 2018
pool-1-thread-1 Start. Time = Thu May 31 10:22:52 CST 2018
pool-1-thread-4 End. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-1 End. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-2 End. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-5 End. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-3 End. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-5 Start. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-2 Start. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-1 Start. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-4 Start. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-3 Start. Time = Thu May 31 10:22:57 CST 2018
pool-1-thread-5 End. Time = Thu May 31 10:23:02 CST 2018
pool-1-thread-1 End. Time = Thu May 31 10:23:02 CST 2018
pool-1-thread-2 End. Time = Thu May 31 10:23:02 CST 2018
pool-1-thread-3 End. Time = Thu May 31 10:23:02 CST 2018
pool-1-thread-4 End. Time = Thu May 31 10:23:02 CST 2018
Finished all threads

3.7.2 shutdown() vs shutdowNow()

shutdown(): 不能再向執行緒池提交新的任務,但是已經執行的任務將被完成

shutdownNow():停止執行中的任務,返回的是等待執行的任務列表

3.7.3 isShutdown() vs isTerminated()

isShutdown(): 呼叫shutdown()或者shutdownnow()返回true,表示程式正在關閉,並非所有任務完成執行

isTerminated():當所有執行緒執行的任務完成執行返回true,需要注意的是,必須在shutdown或者shutdownNow方法後呼叫,否則永遠不返回true。

四 ScheduledThreadPoolExecutor詳解

4.1 簡介

ScheduledThreadPoolExecutor用來按照給定的延遲執行任務或者定期執行任務。

ScheduledThreadPoolExecutor使用的任務佇列DelayQueue封裝了一個PriorityQueue,PriorityQueue會對佇列中的任務進行排序,執行所需時間短的放在前面先被執行(ScheduledFutureTask的time變數小的先執行),如果執行所需時間相同則先提交的任務將被先執行(ScheduledFutureTask的squenceNumber變數小的先執行)。

  • ScheduledThreadPoolExecutor和Timer的比較:
  • Timer對時鐘變化敏感,ScheduledThreadPoolExecutor則不會;
  • Timer只有一個執行執行緒,因此長時間執行的任務可以延遲其他任務。 ScheduledThreadPoolExecutor可以配置任意數量的執行緒。 此外,如果你想(通過提供ThreadFactory),你可以完全控制建立的執行緒;
  • 在TimerTask中丟擲的執行時異常會殺死一個執行緒,從而導致Timer宕機:-( …即計劃任務將不再執行。ScheduledThreadExecutor不僅捕獲執行時異常,還允許您在需要時處理它們(通過重寫afterExecute方法 ThreadPoolExecutor)。丟擲異常的任務將被取消,但其他任務將繼續執行。

綜上,在JDK1.5之後,你沒有理由再使用Timer進行任務排程了。

備註: Quartz是一個由java編寫的任務排程庫,由OpenSymphony組織開源出來。在實際專案開發中使用Quartz的還是居多,比較推薦使用Quartz。因為Quartz理論上能夠同時對上萬個任務進行排程,擁有豐富的功能特性,包括任務排程、任務持久化、可叢集化、外掛等等。

看下,ScheduledThreadPoolExecutor的構造方法,主要是看corePoolSize,maxnumPoolSize引數設定,看其中一個構造方法就行。

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

 corePoolSize設定為0,maxnumPoolSize設定為Integer.MAX_VALUE,當提交任務的速度高於執行緒池處理的速度,會不斷建立新的執行緒,大量消耗系統資源。

4.2 ScheduledThreadPoolExecutor執行機制 

ScheduledThreadPoolExecutorè¿è¡æºå¶

ScheduledThreadPoolExecutor的執行分為兩步:

1.  當呼叫了ScheduledThreadPoolExecutor的scheduleAtFixedRate()或者sheduleWithFixedDelay()方法時,會向DelayQueue中新增一個實現了RunnableScheduledFutur介面的ScheduledFutureTask 

2. 執行緒池中的執行緒從佇列中取出ScheduledFutureTask 然後執行。

ScheduledThreadPoolExecutor為了實現週期性的執行任務,對ThreadPoolExecutor做了如下修改:

  • 使用 DelayQueue 作為任務佇列;
  • 獲取任務的方不同
  • 執行週期任務後,增加了額外的處理

4.3 ScheduledThreadPoolExecutor執行週期任務的步驟

ScheduledThreadPoolExecutoræ§è¡å¨æä»»å¡ç步骤

執行緒1從DelayQueue中獲取已到期的ScheduledFutureTask(DelayQueue.take())。到期任務是指ScheduledFutureTask的time大於等於當前系統的時間;
執行緒1執行這個ScheduledFutureTask;
執行緒1修改ScheduledFutureTask的time變數為下次將要被執行的時間;
執行緒1把這個修改time之後的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。

4.4 ScheduledThreadPoolExecutor使用示例

建立一個簡單的實現Runnable介面的類(我們上面的例子已經實現過)

測試程式使用ScheduledExecutorService和ScheduledThreadPoolExecutor實現的java排程。
 

/**
 * 使用ScheduledExecutorService和ScheduledThreadPoolExecutor實現的java排程程式示例程式。
 */
public class ScheduledThreadPoolDemo {

    public static void main(String[] args) throws InterruptedException {

        //建立一個ScheduledThreadPoolExecutor物件
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
        //計劃在某段時間後執行
        System.out.println("Current Time = "+new Date());
        for(int i=0; i<3; i++){
            Thread.sleep(1000);
            WorkerThread worker = new WorkerThread("do heavy processing");
            //建立並執行在給定延遲後啟用的單次操作。 
            scheduledThreadPool.schedule(worker, 10, TimeUnit.SECONDS);
        }

        //新增一些延遲讓排程程式產生一些執行緒
        Thread.sleep(30000);
        System.out.println("Current Time = "+new Date());
        //關閉執行緒池
        scheduledThreadPool.shutdown();
        while(!scheduledThreadPool.isTerminated()){
            //等待所有任務完成
        }
        System.out.println("Finished all threads");
    }

}

執行結果:

Current Time = Wed May 30 17:11:16 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:11:27 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:11:28 CST 2018
pool-1-thread-3 Start. Time = Wed May 30 17:11:29 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:11:32 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:11:33 CST 2018
pool-1-thread-3 End. Time = Wed May 30 17:11:34 CST 2018
Current Time = Wed May 30 17:11:49 CST 2018
Finished all threads

4.4.1 ScheduledExecutorService scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)方法

我們可以使用ScheduledExecutorService scheduleAtFixedRate方法來安排任務在初始延遲後執行,然後在給定的時間段內執行。

時間段是從池中第一個執行緒的開始,因此如果您將period指定為1秒並且執行緒執行5秒,那麼只要第一個工作執行緒完成執行,下一個執行緒就會開始執行。

for (int i = 0; i < 3; i++) {
	Thread.sleep(1000);
	WorkerThread worker = new WorkerThread("do heavy processing");
	// schedule task to execute at fixed rate
	scheduledThreadPool.scheduleAtFixedRate(worker, 0, 10,
	TimeUnit.SECONDS);
}

執行結果:

Current Time = Wed May 30 17:47:09 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:47:10 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:47:11 CST 2018
pool-1-thread-3 Start. Time = Wed May 30 17:47:12 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:47:15 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:47:16 CST 2018
pool-1-thread-3 End. Time = Wed May 30 17:47:17 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:47:20 CST 2018
pool-1-thread-4 Start. Time = Wed May 30 17:47:21 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:47:22 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:47:25 CST 2018
pool-1-thread-4 End. Time = Wed May 30 17:47:26 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:47:27 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:47:30 CST 2018
pool-1-thread-3 Start. Time = Wed May 30 17:47:31 CST 2018
pool-1-thread-5 Start. Time = Wed May 30 17:47:32 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:47:35 CST 2018
pool-1-thread-3 End. Time = Wed May 30 17:47:36 CST 2018
pool-1-thread-5 End. Time = Wed May 30 17:47:37 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:47:40 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:47:41 CST 2018
Current Time = Wed May 30 17:47:42 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:47:45 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:47:46 CST 2018
Finished all threads

Process finished with exit code 0

 

4.4.2 ScheduledExecutorService scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)方法

ScheduledExecutorService scheduleWithFixedDelay方法可用於以初始延遲啟動週期性執行,然後以給定延遲執行。 延遲時間是執行緒完成執行的時間。

for (int i = 0; i < 3; i++) {
	Thread.sleep(1000);
	WorkerThread worker = new WorkerThread("do heavy processing");
	scheduledThreadPool.scheduleWithFixedDelay(worker, 0, 1,
	TimeUnit.SECONDS);
}

執行結果:

Current Time = Wed May 30 17:58:09 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:58:10 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:11 CST 2018
pool-1-thread-3 Start. Time = Wed May 30 17:58:12 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:58:15 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:16 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:58:16 CST 2018
pool-1-thread-3 End. Time = Wed May 30 17:58:17 CST 2018
pool-1-thread-4 Start. Time = Wed May 30 17:58:17 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:18 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:58:21 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:58:22 CST 2018
pool-1-thread-4 End. Time = Wed May 30 17:58:22 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:23 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:23 CST 2018
pool-1-thread-4 Start. Time = Wed May 30 17:58:24 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:58:27 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:28 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:58:28 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:29 CST 2018
pool-1-thread-4 End. Time = Wed May 30 17:58:29 CST 2018
pool-1-thread-4 Start. Time = Wed May 30 17:58:30 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:58:33 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:34 CST 2018
pool-1-thread-1 Start. Time = Wed May 30 17:58:34 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:35 CST 2018
pool-1-thread-4 End. Time = Wed May 30 17:58:35 CST 2018
pool-1-thread-4 Start. Time = Wed May 30 17:58:36 CST 2018
pool-1-thread-1 End. Time = Wed May 30 17:58:39 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:40 CST 2018
pool-1-thread-5 Start. Time = Wed May 30 17:58:40 CST 2018
pool-1-thread-4 End. Time = Wed May 30 17:58:41 CST 2018
pool-1-thread-2 Start. Time = Wed May 30 17:58:41 CST 2018
Current Time = Wed May 30 17:58:42 CST 2018
pool-1-thread-5 End. Time = Wed May 30 17:58:45 CST 2018
pool-1-thread-2 End. Time = Wed May 30 17:58:46 CST 2018
Finished all threads

4.4.3 scheduleWithFixedDelay() vs scheduleAtFixedRate()

scheduleAtFixedRate(…)將延遲視為兩個任務開始的差異(定期呼叫)
scheduleWithFixedDelay(…)將延遲視為一個任務結束與下一個任務開始之間的差異

scheduleAtFixedRate(): 建立並執行在給定的初始延遲之後,隨後以給定的時間段首先啟用的週期性動作; 那就是執行將在initialDelay之後開始,然後initialDelay+period ,然後是initialDelay + 2 * period ,等等。 如果任務的執行遇到異常,則後續的執行被抑制。 否則,任務將僅通過取消或終止執行人終止。 如果任務執行時間比其週期長,則後續執行可能會遲到,但不會同時執行。
scheduleWithFixedDelay() : 建立並執行在給定的初始延遲之後首先啟用的定期動作,隨後在一個執行的終止和下一個執行的開始之間給定的延遲。 如果任務的執行遇到異常,則後續的執行被抑制。 否則,任務將僅通過取消或終止執行終止。
 

五 各種執行緒池的適用場景介紹

FixedThreadPool: 適用於為了滿足資源管理需求,而需要限制當前執行緒數量的應用場景。它適用於負載比較重的伺服器;

SingleThreadExecutor: 適用於需要保證順序地執行各個任務並且在任意時間點,不會有多個執行緒是活動的應用場景。

CachedThreadPool: 適用於執行很多的短期非同步任務的小程式,或者是負載較輕的伺服器;

ScheduledThreadPoolExecutor: 適用於需要多個後臺執行週期任務,同時為了滿足資源管理需求而需要限制後臺執行緒的數量的應用場景,

SingleThreadScheduledExecutor: 適用於需要單個後臺執行緒執行週期任務,同時保證順序地執行各個任務的應用場景。

轉載: https://blog.csdn.net/qq_34337272/article/details/79959271