為什么要使用執行緒池
-
創建/銷毀執行緒需要消耗系統資源,執行緒池可以復用已創建的執行緒,
-
控制并發的數量,并發數量過多,可能會導致資源消耗過多,從而造成服務器崩潰,(主要原因)
-
可以對執行緒做統一管理,
JUC下執行緒池的體系結構
創建執行緒池的兩種方法
-
使用ThreadPoolExecutor的構造方法創建
public class ThreadPoolTest1 { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 8, 1, TimeUnit.SECONDS , new ArrayBlockingQueue<>(5) , Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); for (int i = 0; i < 11; i++) { pool.execute( () -> { System.out.println(Thread.currentThread().getName()); } ); } }
-
使用Executors這個工具類來實作
JDK工具類為我們提供了四種常用的執行緒池,其實它們的底層原始碼都是呼叫ThreadPoolExecutor來實作的,傳遞的執行緒池引數不同罷了,
四種常見的執行緒池
工程中我們都是使用第一種方法來創建執行緒池,這樣的處理方式讓寫的同學更加明確執行緒池的運行規則,規避資源耗盡的?險(OOM)
執行緒池ThreadPoolExecutor的七大引數
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- corePoolSize:核心執行緒的最大值,相當于正式員工,到點才下班
- maximumPoolSize:核心執行緒+非核心執行緒的最大值,非核心執行緒相當于臨時工,核心執行緒處理不過來才會激活非核心執行緒,業務量低時先下班
- keepAliveTime:非核心執行緒閑置超時時長,超時了非核心執行緒被銷毀
- TimeUnit unit:keepAliveTime的時間單位
- workQueue:阻塞佇列,執行緒池的底層也用了阻塞佇列,維護等待執行的執行緒物件
- ThreadFactory threadFactory:創建執行緒的工程,一般使用Excetory的默認實作默認實作
- RejectedExecutionHandler handler:阻塞佇列滿了之后對新來執行緒的拒絕策略,默認有四種
- ThreadPoolExecutor.AbortPolicy:默認拒絕處理策略,丟棄任務并拋出RejectedExecutionException例外,
- ThreadPoolExecutor.DiscardPolicy:丟棄新來的任務,但是不拋出例外,
- ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列頭部(最舊的)的任務,然后重新嘗試執行程式(如果再次失敗,重復此程序),
- ThreadPoolExecutor.CallerRunsPolicy:回傳給上一步,由呼叫執行緒處理該任務,
執行緒池調度的策略

執行緒池調度的核心是execute方法,總結完就是上圖
// JDK 1.8
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 1.當前執行緒數小于corePoolSize,則呼叫addWorker創建核心執行緒執行任務
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2.如果不小于corePoolSize,則將任務添加到workQueue佇列,
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 2.1 如果isRunning回傳false(狀態檢查),則remove這個任務,然后執行拒絕策略,
if (! isRunning(recheck) && remove(command))
reject(command);
// 2.2 執行緒池處于running狀態,但是沒有執行緒,則創建執行緒
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3.如果放入workQueue失敗,則創建非核心執行緒執行任務,
// 如果這時創建非核心執行緒失敗(當前執行緒總數不小于maximumPoolSize時),就會執行拒絕策略,
else if (!addWorker(command, false))
reject(command);
}
這個對執行緒調度的原始碼沒有深入分析,如addWord函式,拒絕策略是怎么實作的等,以后會再專門寫一篇文章,可以參考《并發編程之美》的原始碼分析,
執行execute方法和submit方法有何區別?
- execute()方法用于提交不需要回傳值的任務,所以無法判斷任務是否被執行緒池執行成功與否;
- submit()方法用于提交需要回傳值的任務,執行緒池會回傳一個Future型別的物件,通過這個Future物件可以判斷任務是否執行成功,并且可以通過Future的get()方法來獲取回傳值,
這也可以看到執行緒池的又一優點:靈活,
參考
-
JavaGuide
-
深入淺出Java多執行緒-執行緒池原理
-
Java執行緒池實作原理及其在美團業務中的實踐
還沒仔細研究,粗略看了下,怎么很不錯,值得參考
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/280489.html
標籤:Java
