Java執行緒池的使用和原理
Java中的執行緒池是運用場景最多的併發框架,幾乎所有需要非同步或併發執行任務的程式都可以使用執行緒池。在開發過程中,合理地使用執行緒池能夠帶來3個好處。
- 降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗。
- 提高響應速度。當任務到達時,任務可以不需要等到執行緒建立就能立即執行。
- 提高執行緒的可管理性。
執行緒是稀缺資源,如果無限制地建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一分配、調優和監控。
執行緒池的使用
建立執行緒池
可以通過ThreadPoolExecutor來建立一個執行緒池:
public ThreadPoolExecutor(int corePoolSize,//核心執行緒數量
int maximumPoolSize,//最大執行緒數
long keepAliveTime,//超時時間,超出核心執行緒數量以外的執行緒空餘存活時間
TimeUnit unit,//存活時間單位
BlockingQueue<Runnable> workQueue,//儲存執行任務的佇列
ThreadFactory threadFactory,//建立新執行緒使用的工廠
RejectedExecutionHandler handler)//當任務無法執行的時候的處理方式
workQueue引數可以選擇以下幾個阻塞佇列:
ArrayBlockingQueue:是一個基於陣列結構的有界阻塞佇列,此佇列按FIFO(先進先出)原則對元素進行排序。
LinkedBlockingQueue:一個基於連結串列結構的阻塞佇列,此佇列按FIFO排序元素,吞吐量通常要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個佇列。
SynchronousQueue:一個不儲存元素的阻塞佇列。每個插入操作必須等到另一個執行緒呼叫移除操作,提交的任務不會被真實的儲存,而總是將新任務提交給執行緒執行,如果沒有空閒的執行緒,則嘗試建立新的執行緒,如果執行緒的數量已經達到了最大值,則執行拒絕策略,靜態工廠方法Executors.newCachedThreadPool使用了這個佇列。
PriorityBlockingQueue:一個具有優先順序的無限阻塞佇列。
handler引數可以以下幾個拒絕策略:
AbortPolicy:直接丟擲異常。
CallerRunsPolicy:使用呼叫者所線上程來執行任務。
DiscardOldestPolicy:丟棄佇列裡最近的一個任務,並執行當前任務。
DiscardPolicy:不處理,丟棄掉。
當然,也可以根據應用場景需要來實現RejectedExecutionHandler介面自定義策略。如記錄日誌或持久化儲存不能處理的任務。
向執行緒池提交任務
可以使用兩個方法向執行緒池提交任務,分別為execute()和submit()方法。
execute()方法用於提交不需要返回值的任務,所以無法判斷任務是否被執行緒池執行成功。execute()方法輸入的任務是一個Runnable類的例項。
threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});
submit()方法用於提交需要返回值的任務,引數可以是是Runnable或Callable。執行緒池會返回一個future型別的物件,通過這個future物件可以判斷任務是否執行成功,並且可以通過future的get()方法來獲取返回值,get()方法會阻塞當前執行緒直到任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞當前執行緒一段時間後立即返回,這時候有可能任務沒有執行完。
Future<Object> future = executor.submit(harReturnValuetask);
try {
Object s = future.get();
} catch (InterruptedException e) {
// 處理中斷異常
} catch (ExecutionException e) {
// 處理無法執行任務異常
} finally {
// 關閉執行緒池
executor.shutdown();
}
關閉執行緒池
可以通過呼叫執行緒池的shutdown或shutdownNow方法來關閉執行緒池。它們的原理是遍歷執行緒池中的工作執行緒,然後逐個呼叫執行緒的interrupt方法來中斷執行緒,所以無法響應中斷的任務可能永遠無法終止。但是它們存在一定的區別,shutdownNow首先將執行緒池的狀態設定成STOP,然後嘗試停止所有的正在執行或暫停任務的執行緒,並返回等待執行任務的列表,而shutdown只是將執行緒池的狀態設定成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的執行緒。
只要呼叫了這兩個關閉方法中的任意一個,isShutdown方法就會返回true。當所有的任務都已關閉後,才表示執行緒池關閉成功,這時呼叫isTerminaed方法會返回true。至於應該呼叫哪一種方法來關閉執行緒池,應該由提交到執行緒池的任務特性決定,通常呼叫shutdown方法來關閉執行緒池,如果任務不一定要執行完,則可以呼叫shutdownNow方法。
執行緒池的監控
如果在系統中大量使用執行緒池,則有必要對執行緒池進行監控,方便在出現問題時,可以根據執行緒池的使用狀況快速定位問題。可以通過執行緒池提供的引數進行監控,在監控執行緒池的時候可以使用以下屬性。
taskCount:執行緒池需要執行的任務數量。
completedTaskCount:執行緒池在執行過程中已完成的任務數量,小於或等於taskCount。
largestPoolSize:執行緒池裡曾經建立過的最大執行緒數量。通過這個資料可以知道執行緒池是否曾經滿過。如該數值等於執行緒池的最大大小,則表示執行緒池曾經滿過。
getPoolSize:執行緒池的執行緒數量。如果執行緒池不銷燬的話,執行緒池裡的執行緒不會自動銷燬,所以這個大小隻增不減。
getActiveCount:獲取活動的執行緒數。
通過擴充套件執行緒池進行監控。可以通過繼承執行緒池來自定義執行緒池,重寫執行緒池的beforeExecute、afterExecute和terminated方法,也可以在任務執行前、執行後和執行緒池關閉前執行一些程式碼來進行監控。例如,監控任務的平均執行時間、最大執行時間和最小執行時間等。
當向執行緒池提交一個任務之後,執行緒池的處理流程如下:
執行緒池的原理
執行緒池用一個AtomicInteger來儲存 [執行緒數量] 和 [執行緒池狀態] ,一個int數值一共有32位,高3位用於儲存執行狀態,低29位用於儲存執行緒數量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //一個原子操作類
private static final int COUNT_BITS = Integer.SIZE - 3; //32-3
private static final int CAPACITY = (1 << COUNT_BITS) - 1; //將1的二進位制向右位移29位,再減
1表示最大執行緒容量
//執行狀態儲存在int值的高3位 (所有數值左移29位)
private static final int RUNNING = -1 << COUNT_BITS;// 接收新任務,並執行佇列中的任務
private static final int SHUTDOWN = 0 << COUNT_BITS;// 不接收新任務,但是執行佇列中的任務
private static final int STOP = 1 << COUNT_BITS;// 不接收新任務,不執行佇列中的任務,中
斷正在執行中的任務
private static final int TIDYING = 2 << COUNT_BITS; //所有的任務都已結束,執行緒數量為0,處
於該狀態的執行緒池即將呼叫terminated()方法
private static final int TERMINATED = 3 << COUNT_BITS;// terminated()方法執行完成
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; } //獲取執行狀態
private static int workerCountOf(int c) { return c & CAPACITY; } //獲取執行緒數量
execute()方法原始碼:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {//1.當前池中執行緒比核心數少,新建一個執行緒執行任務
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {//2.核心池已滿,但任務佇列未滿,新增到佇列
中
int recheck = ctl.get();
//任務成功新增到佇列以後,再次檢查是否需要新增新的執行緒,因為已存在的執行緒可能被銷燬了
if (! isRunning(recheck) && remove(command))
reject(command);//如果執行緒池處於非執行狀態,並且把當前的任務從任務佇列中移除成功,則拒
絕該任務
else if (workerCountOf(recheck) == 0)//如果之前的執行緒已被銷燬完,新建一個執行緒
addWorker(null, false);
}
else if (!addWorker(command, false)) //3.核心池已滿,佇列已滿,試著建立一個新執行緒
reject(command); //如果建立新執行緒失敗了,說明執行緒池被關閉或者執行緒池完全滿了,拒絕任務
}
相關文章
- Java執行緒池二:執行緒池原理Java執行緒
- JAVA執行緒池的原理及使用Java執行緒
- SpringBoot執行緒池和Java執行緒池的實現原理Spring Boot執行緒Java
- Java 執行緒池執行原理分析Java執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- JAVA執行緒池的使用Java執行緒
- Java面試必問之執行緒池的建立使用、執行緒池的核心引數、執行緒池的底層工作原理Java面試執行緒
- Java執行緒池核心原理剖析Java執行緒
- 圖解Java執行緒池原理圖解Java執行緒
- Java執行緒池原理及分析Java執行緒
- 從使用到原理學習Java執行緒池Java執行緒
- Java 執行緒池(ThreadPoolExecutor)原理分析與使用Java執行緒thread
- java多執行緒:執行緒池原理、阻塞佇列Java執行緒佇列
- 執行緒池原理執行緒
- 多執行緒-執行緒池的概述和使用執行緒
- Java 執行緒池的原理與實現Java執行緒
- Java執行緒池原始碼及原理Java執行緒原始碼
- 詳解執行緒池的作用及Java中如何使用執行緒池執行緒Java
- [轉]Java 執行緒池的原理與實現Java執行緒
- java 四種執行緒池的使用Java執行緒
- Java四種執行緒池的使用Java執行緒
- Java併發程式設計:Java執行緒池核心ThreadPoolExecutor的使用和原理分析Java程式設計執行緒thread
- 執行緒池原理初探執行緒
- 執行緒和執行緒池執行緒
- 執行緒池的建立和使用,執行緒池原始碼初探(篇一)執行緒原始碼
- Java多執行緒——執行緒池Java執行緒
- 深入Java原始碼理解執行緒池原理Java原始碼執行緒
- 《Java 高階篇》七:執行緒和執行緒池Java執行緒
- Java執行緒池使用說明Java執行緒
- Java執行緒池Java執行緒
- java 執行緒池Java執行緒
- 執行緒和執行緒池的理解與java簡單例子執行緒Java單例
- 執行緒池的實現原理執行緒
- Java 併發:執行緒、執行緒池和執行器全面教程Java執行緒
- Java中命名執行器服務執行緒和執行緒池Java執行緒
- 執行緒池的使用執行緒
- java執行緒池趣味事:這不是執行緒池Java執行緒
- 多執行緒:執行緒池理解和使用總結執行緒