JUC的執行緒池架構
1.Executor
Executor是Java非同步任務的執行者介面,目標是執行目標任務。Executor作為執行者角色,目的是提供一種將“任務提交者”與“任務執行者”分離的機制。它只有一個函式式方法:
public interface Executor {
void execute(Runnable command);
}
2.ExecutorService
ExecutorService繼承於Executor。它對外提供非同步任務的接收服務。ExecutorService提供了“接受非同步任務並轉交給執行者”的方法,比如submit、invoke方法等。具體如下:
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
3.AbstractExecutorService
AbstractExecutorService是一個抽象類,它實現了ExecutorService介面。AbstractExecutorService存在的目的是為ExecutorService中的介面提供預設實現。(模板模式)
4.ThreadPoolExecutor
大名鼎鼎的執行緒池實現類,繼承於AbstractExecutorService。它是核心實現類,它可以預先提供指定數量的可重用執行緒,可以對執行緒進行管理和監控。
5.ScheduledExecutorService
她繼承於ExecutorService。是一個完成延時和週期性任務的介面。
6.Executors
是一個靜態工廠類,內建的靜態工廠方法可以理解為快捷建立執行緒池的方法。
Executors的4種快捷建立執行緒池的方法
newSingleThreadExecutor 建立只有一個執行緒的執行緒池
newFixedThreadPool 建立固定大小的執行緒池
newCachedThreadPool 建立一個不限制執行緒數量的執行緒池,任何提交的任務都立即執行,空閒執行緒會及時回收
newScheduledThreadPool 建立一個可定期或延時執行任務的執行緒池
- newSingleThreadExecutor
public static void main(String[] args) {
final AtomicInteger integer = new AtomicInteger(0);
ExecutorService pool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread() + " :doing" + "-" + integer.incrementAndGet());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
pool.shutdown();
}
Thread[pool-1-thread-1,5,main] :doing-1
Thread[pool-1-thread-1,5,main] :doing-2
Thread[pool-1-thread-1,5,main] :doing-3
Thread[pool-1-thread-1,5,main] :doing-4
Thread[pool-1-thread-1,5,main] :doing-5
場景:任務按照提交順序,一個任務一個任務逐個執行。
以上程式碼最後呼叫shutdown來關閉執行緒池。執行shutdown方法後,執行緒池狀態變為shutdown,執行緒池將拒絕新任務,不能再往執行緒池中新增新任務。此時,執行緒池不會立刻退出,直到執行緒池中的任務處理完成後才會退出。還有一個shutdownNow方法,執行這個後,執行緒狀態變為stop,試圖停止所有正在執行的執行緒,並且不再處理阻塞佇列中等待的任務,會返回那些未執行的任務。
- newFixedThreadPool
ExecutorService pool = Executors.newFixedThreadPool(3);
Thread[pool-1-thread-1,5,main] :doing-1
Thread[pool-1-thread-3,5,main] :doing-2
Thread[pool-1-thread-2,5,main] :doing-3
Thread[pool-1-thread-3,5,main] :doing-4
Thread[pool-1-thread-1,5,main] :doing-5
適用場景:需要任務長期執行的場景。“固定數量的執行緒池”能穩定的保證一個數,避免頻繁 回收和建立執行緒,適用於CPU密集型的任務,在CPU被執行緒長期佔用的情況下,能確保少分配執行緒。
弊端:內部使用無界佇列存放任務,當有大量任務,佇列無限增大,伺服器資源迅速耗盡。
newFixedThreadPool工廠方法返回一個ThreadPoolExecutor例項,該執行緒池例項的corePoolSize數量為引數nThread,其maximumPoolSize數量也為引數nThread,其workQueue屬性的值為LinkedBlockingQueue
- newCachedThreadPool
執行緒池內的某些執行緒無事可幹成為空閒執行緒,可以靈活回收這些空閒執行緒。
ExecutorService pool = Executors.newCachedThreadPool();
Thread[pool-1-thread-5,5,main] :doing-5
Thread[pool-1-thread-1,5,main] :doing-1
Thread[pool-1-thread-2,5,main] :doing-2
Thread[pool-1-thread-3,5,main] :doing-3
Thread[pool-1-thread-4,5,main] :doing-4
特點:在執行任務時,如果池內所有執行緒忙,則會新增新執行緒來處理。不會限制執行緒的大小,完全依賴於作業系統能夠建立的最大執行緒大小。如果存量執行緒超過了處理任務數量,就會回收執行緒。、
適用場景:快速處理突發性強、耗時短的任務場景,如Netty的NIO處理場景、REST API介面的瞬時削峰場景。
弊端:沒有最大執行緒數量限制,如果大量的非同步任務提交,伺服器資源可能耗盡。
- newScheduledThreadPool
public static void main(String[] args) {
final AtomicInteger integer = new AtomicInteger(0);
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 5; i++) {
pool.scheduleAtFixedRate(
() -> {
System.out.println(Thread.currentThread() + " :doing" + "-" + integer.incrementAndGet());
}, 0, 500, TimeUnit.MILLISECONDS);
// 0表示首次執行任務的執行時間,500表示每次執行任務的間隔時間
}
// pool.shutdown();
}
因為可以週期性執行任務,所以不shutdown。
適用場景:週期性執行任務的場景。
執行緒池的標準建立方式
使用ThreadPoolExecutor構造方法建立,一個比較重要呃構造器如下:
public ThreadPoolExecutor(int corePoolSize,核心執行緒數
int maximumPoolSize, 最大執行緒數
long keepAliveTime, TimeUnit unit, 空閒時間
BlockingQueue<Runnable> workQueue, 阻塞佇列
ThreadFactory threadFactory, 執行緒工廠(執行緒產生方式)
RejectedExecutionHandler handler 拒絕策略) {
...
}
1.核心和最大執行緒數量
接收新任務時,並且當前工作執行緒池數少於核心執行緒數量,即使有工作執行緒是空閒的,它也會建立新執行緒處理任務,直到達到核心執行緒數。
2.BlockingQueue
阻塞佇列用於暫時接收任務。
3.KeepAliveTime
設定執行緒最大空閒時長,如果超過這個時間,非核心執行緒會被回收。當然,也可以呼叫allowCoreThreadTimeOut方法將超時策略應用到核心執行緒。
執行緒池的任務排程流程
- 工作執行緒數量小於核心執行緒數量,執行新任務時會優先建立執行緒,而不是獲取空閒執行緒。
- 任務數量大於核心執行緒數量,新任務將被加入阻塞佇列中。執行任務時,也是先從阻塞佇列中獲取任務。
- 在核心執行緒用完,阻塞佇列已滿的情況下,會建立非核心執行緒處理新任務。
- 在如果執行緒池總數超過maximumPoolSize,執行緒池會拒絕接收任務,為新任務執行拒絕策略。
ThreadFactory(執行緒工廠)
建立執行緒方式
阻塞佇列
阻塞佇列與普通度列相比:阻塞佇列為空時,會阻塞當前執行緒的元素獲取操作。當佇列中有元素,被阻塞的執行緒會被自動喚醒。
BlockingQueue是JUC包的一個超級介面,比較常用的實現類有:
(1)ArrayBlockingQueue:陣列佇列
(2)LinkedBlockingQueue:連結串列佇列
(3)PriorityBlockingQueue:優先順序佇列
(4)DelayQueue:延遲佇列
(5)SynchronousQueue:同步佇列
排程器的鉤子方法
ThreadPoolExecutor為每個任務執行前後都提供了鉤子方法。
// 任務執行之前的鉤子方法(前鉤子)
protected void beforeExecute(Thread t, Runnable r) { }
// 之後(後鉤子)
protected void afterExecute(Runnable r, Throwable t) { }
// 終止(停止鉤子)
protected void terminated() { }
beforeExecute:可用於重新初始化ThreadLocal執行緒本地變數例項、更新日誌記錄、計時統計等。
afterExecute:更新日誌記錄、計時統計等。
terminated:Executor終止時呼叫。
演示一下前鉤子。
public class TestMain {
public static void main(String[] args) {
final ThreadPoolExecutor pool = new ThreadPoolExecutor(
2,
4,
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2)) {
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("前鉤子嗷 ~ ~ ~ ");
}
};
for (int i = 0; i < 5; i++) {
pool.execute(() -> {
System.out.println("你誰啊");
});
}
}
}
執行緒池拒絕策略
任務被拒絕有兩種情況:
- 執行緒池已經關閉。
- 工作佇列已滿且最大執行緒數已滿。
拒絕策略有以下實現:
- AbortPolicy:拒絕策略。拋異常。
- DiscardPolicy:拋棄策略。丟棄新來的任務。
- DiscardOldestPolicy:拋棄最老任務策略。因為佇列是隊尾進對頭出,所以每次都是移除隊頭元素後再入隊。
- CallerRunsPolicy:呼叫者執行策略。提交任務執行緒自己執行任務,不使用執行緒池中的執行緒。
- 自定義策略。實現RejectExecutionHandler介面的rejectedExecution方法。
Re
《Java高併發程式設計》
(之後補原始碼)