1. 線程池的原理
ThreadPoolExecutor 是線程池的核心類,繼承了AbstractExecutorService類,AbstractExecutorService實現ExecutorService接口,ExecutorService接口繼承Executor
1.1 構造方法中的參數
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
- corePoolSize
當線程池的線程數目達到corePoolSize的時候,就會把的任務放到緩存隊列中 - maximumPoolSize
表線程池最多能創建多少的線程數 - keepAliveTime
表示線程沒有任務執行時,保持多久會結束,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用 - unit
keepAliveTime的時間單位 - workQueue
阻塞隊列,存儲等待執行的任務 ,有ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue; - threadFactory:
線程工廠,主要用來創建線程 - handler:
表示當拒絕處理任務時的策略,有以下四種取值
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
1.2. ThreadPoolExecutor的方法
- execute() 向線程池提交一個任務,交由線程池去執行
- submit()也是用來向線程池提交任務的,但是它和execute()方法不同,它能夠返回任務執行的結果,實際上還是調用的execute()方法,只不過它利用了Future來獲取任務執行結果
- shutdown()和shutdownNow()是用來關閉線程池
1.3.線程池狀態
volatile int runState;
static final int RUNNING = 0;
static final int SHUTDOWN = 1;
static final int STOP = 2;
static final int TERMINATED = 3;
- runState表示當前線程池的狀態,volatile變量,用來保證線程之間的可見性;
- RUNNING 線程池創建,初始時
- SHUTDOWN 調用了shutdown(),線程不能接受新的任務,等待所有任務執行完畢
- STOP 調用shutdownNow(),此時終止正在執行的任務。
- TERMINATED 所有工作線程已經銷燬,任務緩存隊列已經清空。
1.4.任務提交,根據不同線程數來做相應處理
在ThreadPoolExecutor類中,最核心的任務提交方法是execute()方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
- 如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務,然後將線程放入池子中;
- 如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;
- 如果當前線程池中的線程數目達到maximumPoolSize,則會採取任務拒絕策略進行處理;
- 如果線程池中的線程數量大於 corePoolSize時,線程空閒時間超過keepAliveTime,線程將被終;
1.5 總結:
(1)線程池初始化,默認初始化線程池中是沒有線程的,需要提交任務纔有。實踐中,可以先創建一定數量的線程。prestartCoreThread初始化一個核心線程,prestartAllCoreThread初始化所有核心線程
(2)任務提交的時候
如果線程數目小於corePoolSize,那麼就會創建線程去執行這個任務,將線程放入池子中;
如果maximumPoolSize>線程數目>=corePoolSize,那麼就會將任務添加到任務緩存隊列中(任務緩存隊列workQueue一般用LinkedBlockingQueue),等待空閒線程將其取出。如果添加失敗,說明緩存隊列滿了,需要重新創建一個線程。
如果線程數目達到如果maximumPoolSize,會採取拒絕策略處理。
同時線程數量>corePoolSize的時候,那麼線程空閒時間超過keepAliveTime,線程將會被終結。
(3)拒絕策略
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
(4)線程池關閉
shutdown():等所有任務緩存隊列的任務執行完再關閉,但不會接收新任務。 SHUTDOWN
shutdownNow():嘗試打斷正在執行的任務,並清空任務緩存隊列,返回正在執行的任務 STOP
2.四種線程池
2.1 Executors.newCachedThreadPool()
Executors.newCachedThreadPool(); //創建一個緩衝池,緩衝池容量大小爲Integer.MAX_VALUE
只有非核心線程,最大線程數很大(Int.Max(values)),它會爲每一個任務添加一個新的線程,這邊有一個超時機制,當空閒的線程超過60s內沒有用到的話,就會被回收。缺點就是沒有考慮到系統的實際內存大小。
2.2 Executors.newSingleThreadExecutor()
只有一個核心線程。其他任務進來就進入等待隊列等待。
2.3 Executors.newFixedThreadPool(int)
創建固定數量的核心線程數。
2.4 Executors.newScheduledThreadPool
創建一個定長線程池,支持定時及週期性任務執行。
3.線程池大小的設置
最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目
(1)高併發、任務執行時間短的業務,線程池線程數可以設置爲CPU核數+1,減少線程上下文的切換
(2)併發不高、任務執行時間長的業務要區分開看
假如是業務時間長集中在IO操作上,也就是IO密集型的任務,因爲IO操作並不佔用CPU,所以不要讓所有的CPU閒下來,可以適當加大線程池中的線程數目,讓CPU處理更多的業務
假如是業務時間長集中在計算操作上,也就是計算密集型任務,線程池中的線程數設置得少一些,減少線程上下文的切換
(3)併發高、業務執行時間長,解決這種類型任務的關鍵不在於線程池而在於整體架構的設計,看看這些業務裏面某些數據是否能做緩存是第一步,增加服務器是第二步,至於線程池的設置,設置參考(2)。最後,業務執行時間長的問題,也可能需要分析一下,看看能不能使用中間件對任務進行拆分和解耦。
4.Future&FutureTask
submit 方法 可以返回任務執行後的結果
將實現Callable接口的task進行submint,然後會返回一個用Future接口接收返回,get()方法就是你需要返回的信息,get()的時候,會從completionQueue隊列中去取,然後返回Future接口對象。好處是不用輪詢等待,直接get的話要等任務執行完,否則要調用awaitDone阻塞,輪詢查看futureTask的狀態。
Callable是一個泛型接口,call()函數返回的類型就是傳遞進來的泛型實參類型
public interface Callable<V> {
V call() throws Exception;
}
FutureTask
實現了RunnableFuture接口,RunnableFuture接口繼承了Runnable,Future。只有當運算完成的時候結果才能取回,如果運算尚未完成get方法將會阻塞
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
public class FutureTask<V> implements RunnableFuture<V>
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f
}
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}