(四)java 執行緒,執行緒池的使用
為什麼要使用執行緒池?
建立執行緒是簡單的,但啟動後的執行緒猶如脫繮野馬,難於管理,特別是多執行緒使用場景,執行緒之間的互相競爭,可能使 cpu 花費更多時間在各個執行緒之間切換,而且執行緒結束後的回收由垃圾回收控制,你不知道工作結束的執行緒還會存活多久,是否持有著什麼資源。而且執行緒物件提供使用方法有限,無法提供定時啟動、執行緒併發數控制等操作,所以,執行緒池出現了。
執行緒池提供了對執行緒的管理,讓執行緒稍微能得到控制(中斷、取消等);併發執行緒數的控制,提高系統資源使用率,避免過多的資源競爭;複用執行緒,減少執行緒物件建立,提高執行緒啟動效率以及降低系統資源消耗;提供延時啟動、定期執行等操作。
執行緒池介紹
具體執行緒池有以下幾個:
ThreadPoolExecutor
繼承AbstractExecutorService
,Executors
提供的預設執行緒池,除了定時執行緒池,其他都是ThreadPoolExecutor
的預設引數構建ScheduledThreadPoolExecutor
可排程執行緒池(定時執行緒池)
繼承ThreadPoolExecutor
實現ScheduledExecutorService
ForkJoinPool
,繼承AbstractExecutorService
特殊執行緒池,分治法(Divide-and-Conquer Algorithm),可以將一個大任務(執行緒)在細分多個小任務執行,直到所有小任務完成才算大任務完成,充分利用多核 cpu 的特性。與ThreadPoolExecutor
ForkJoinPool
可以實現在有限的執行緒數下完成非常多的任務,如使用4個執行緒完成具有父子關係的400萬個任務,而ThreadPoolExecutor
無法選擇優先執行子任務,所以處理400萬的任務,需要400萬個執行緒。ExecutorCompletionService
實現CompletionService
,構造方法傳入Executor
,可以認為是對Executor
的使用封裝。
使用較多的執行緒池是 ThreadPoolExecutor
,本文只介紹這個。對其他型別有興趣的可以去 java.util.concurrent
中檢視。
構造方法:
public ThreadPoolExecutor(int corePoolSize,//核心池數量,併發的執行緒數
int maximumPoolSize,//執行緒池最大容量
long keepAliveTime,//執行緒存活時間
TimeUnit unit,//時間單位
BlockingQueue<Runnable> workQueue,//執行緒快取佇列
ThreadFactory threadFactory,//執行緒建立工廠
RejectedExecutionHandler handler//加入執行緒失敗策略) {
//... 省略
}
向執行緒池提交一個任務,如果核心池未滿,則用該命令作為第一個任務開啟一個新執行緒
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))//true:新增成功 false:新增失敗
return;
c = ctl.get();
}
如果核心池已經滿了,檢查執行緒池最大容量,沒有超過,則加入快取工作佇列,建立新執行緒去執行任務;當執行緒池達到最大容量,並且快取佇列已經飽和時,此時再繼續往執行緒池中新增任務,則會拒絕。
由快取佇列建立的執行緒,如果處於空閒狀態,在一定的時間(keepAliveTime)後將會被回收,而核心池中的執行緒如果 allowCoreThreadTimeOut
該引數為 false
(預設值) 即使處於空閒狀態也不會被回收。
流程圖如下:
使用:
ThreadPoolExecutor pool = new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
pool.execute(()-> print "runnable");
一般來說,除非有特殊的處理要求,否則建議使用Executors
中的方法構造執行緒池,對於執行緒池的核心數、最大容量、工作佇列結構、執行緒工廠等設定,預設構造基本滿足開發需求。
關閉執行緒池:
//不再接受新任務,直到快取區任務執行完畢
public void shutdown(){...}
//不再接受新任務,將快取區任務返回,並清空工作區,中斷執行中的任務
public List<Runnable> shutdownNow() {...}