併發程式設計(四)執行緒池ThreadPoolExecutor
目錄
1.執行緒池的作用:
Java中的執行緒池是運用場景最多的併發框架,幾乎所有需要非同步或併發執行任務的程式都可以使用執行緒池。在開發過程中,合理地使用執行緒池能夠帶來3個好處。
第一:降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。
第三:提高執行緒的可管理性。執行緒是稀缺資源,如果無限制地建立,不僅會消耗系統資源, 還會降低系統的穩定性,使用執行緒池可以進行統一分配、調優和監控。但是,要做到合理利用執行緒池,必須對其實現原理了如指掌。
2.執行緒池的繼承類圖
3.執行緒池的構造方法
圖中可以看到ThreadPoolExecutor有4和構造方法
下面以第四個為例講解
public ThreadPoolExecutor(int corePoolSize, //執行緒池的基本大小 RejectedExecutionHandler handler) //飽和策略{ if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == 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();
}
}
輸出: