1. 程式人生 > >併發程式設計(四)執行緒池ThreadPoolExecutor

併發程式設計(四)執行緒池ThreadPoolExecutor

目錄

6.監控·

1.執行緒池的作用:

Java中的執行緒池是運用場景最多的併發框架,幾乎所有需要非同步或併發執行任務的程式都可以使用執行緒池。在開發過程中,合理地使用執行緒池能夠帶來3個好處。

第一:降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。

第二:提高響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。

第三:提高執行緒的可管理性。執行緒是稀缺資源,如果無限制地建立,不僅會消耗系統資源, 還會降低系統的穩定性,使用執行緒池可以進行統一分配、調優和監控。但是,要做到合理利用執行緒池,必須對其實現原理了如指掌。 

2.執行緒池的繼承類圖

3.執行緒池的構造方法

圖中可以看到ThreadPoolExecutor有4和構造方法 

下面以第四個為例講解

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.1引數具體解釋

1)corePoolSize(執行緒池的基本大小):當提交一個任務到執行緒池時,執行緒池會建立一個線 程來執行任務,即使其他空閒的基本執行緒能夠執行新任務也會建立執行緒,等到需要執行的任務數大於執行緒池基本大小時就不再建立。如果呼叫了執行緒池的prestartAllCoreThreads()方法, 執行緒池會提前建立並啟動所有基本執行緒。

2)runnableTaskQueue(任務佇列):用於儲存等待執行的任務的阻塞佇列。可以選擇以下幾個阻塞佇列。 

·ArrayBlockingQueue是一個基於陣列結構的有界阻塞佇列,此佇列按FIFO(先進先出)原則對元素進行排序。

·LinkedBlockingQueue一個基於連結串列結構的阻塞佇列,此佇列按FIFO排序元素,吞吐量通常要高ArrayBlockingQueue

·SynchronousQueue:一個不儲存元素的阻塞佇列。每個插入操作必須等到另一個執行緒呼叫移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於Linked-BlockingQueue

·PriorityBlockingQueue一個具有優先順序的無限阻塞佇列。

3)maximumPoolSize(執行緒池最大數量):執行緒池允許建立的最大執行緒數。如果佇列滿了,並 且已建立的執行緒數小於最大執行緒數,則執行緒池會再建立新的執行緒執行任務。值得注意的是,如果使用了無界的任務佇列這個引數就沒什麼效果。

 4)ThreadFactory:用於設定建立執行緒的工廠,可以通過執行緒工廠給每個創建出來的執行緒設 置更有意義的名字。使用開源框架guava提供的ThreadFactoryBuilder可以快速給執行緒池裡的執行緒設定有意義的名字,

5)RejectedExecutionHandler(飽和策略):當佇列和執行緒池都滿了,說明執行緒池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略預設情況下是AbortPolicy,表示無法處理新任務時丟擲異常。

在JDK 1.5中Java執行緒池框架提供了以下4種策略。 ·

.AbortPolicy:直接丟擲異常。

·CallerRunsPolicy:只用呼叫者所線上程來執行任務。

·DiscardOldestPolicy:丟棄佇列裡最近的一個任務,並執行當前任務。

·DiscardPolicy:不處理,丟棄掉。 當然,也可以根據應用場景需要來實現RejectedExecutionHandler介面自定義策略。如記錄 日誌或持久化儲存不能處理的任務。

6)keepAliveTime(執行緒活動保持時間):執行緒池的工作執行緒空閒後,保持存活的時間。所以,如果任務很多,並且每個任務執行的時間比較短,可以調大時間,提高執行緒的利用率。

7)TimeUnit(執行緒活動保持時間的單位):可選的單位有天(DAYS)、小時(HOURS)、分鐘(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和納秒(NANOSECONDS,千分之一微秒)。

4.向執行緒池提交任務

可以使用兩個方法向執行緒池提交任務,分別為execute()和submit()方法。

4.1 execute()

execute()方法用於提交不需要返回值的任務,所以無法判斷任務是否被執行緒池執行成功。通過以下程式碼可知execute()方法輸入的任務是一個Runnable類的例項。

4.2 submit()

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

5.關閉執行緒池

可以通過呼叫執行緒池的shutdown或shutdownNow方法來關閉執行緒池。它們的原理是遍歷線 程池中的工作執行緒,然後逐個呼叫執行緒的interrupt方法來中斷執行緒,所以無法響應中斷的任務可能永遠無法終止。但是它們存在一定的區別,shutdownNow首先將執行緒池的狀態設定成 STOP,然後嘗試停止所有的正在執行或暫停任務的執行緒,並返回等待執行任務的列表,而 shutdown只是將執行緒池的狀態設定成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的執行緒。

只要呼叫了這兩個關閉方法中的任意一個,isShutdown方法就會返回true。當所有的任務都已關閉後,才表示執行緒池關閉成功,這時呼叫isTerminaed方法會返回true。至於應該呼叫哪一種方法來關閉執行緒池,應該由提交到執行緒池的任務特性決定,通常呼叫shutdown方法來關閉執行緒池,如果任務不一定要執行完,則可以呼叫shutdownNow方法。

6.監控·

taskCount:執行緒池需要執行的任務數量。

·completedTaskCount:執行緒池在執行過程中已完成的任務數量,小於或等於taskCount。

·largestPoolSize:執行緒池裡曾經建立過的最大執行緒數量。通過這個資料可以知道執行緒池是否曾經滿過。如該數值等於執行緒池的最大大小,則表示執行緒池曾經滿過。

·getPoolSize:執行緒池的執行緒數量。如果執行緒池不銷燬的話,執行緒池裡的執行緒不會自動銷燬,所以這個大小隻增不減。

·getActiveCount:獲取活動的執行緒數。

7.執行緒池的簡單操作

public class ThreadPoolExecutorTest implements Runnable{


    @Override
    public void run() {
        System.out.println("111"+Thread.currentThread().getName());
        //獲取cpu核心數
        System.out.println(Runtime.getRuntime().availableProcessors());
    }

    public static void main(String[] args) {
        ExecutorService executorService =new ThreadPoolExecutor(10,12,20,TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(10));
        Thread thread =new Thread(new ThreadPoolExecutorTest());
        Thread thread1 =new Thread(new ThreadPoolExecutorTest());
        Thread thread2 =new Thread(new ThreadPoolExecutorTest());
        executorService.execute(thread);
        executorService.submit(thread1);
        executorService.submit(thread2);
        //輸出執行緒池存在的執行緒數
        System.out.println(((ThreadPoolExecutor) executorService).getTaskCount());
        //關閉執行緒池
        executorService.shutdown();


    }
}

 

輸出: