在JUC包中,線程池部分本身有很多組件,可以說是前面所分析的各種技術的一個綜合應用。從本文開始,將綜合前面的知識,逐個分析線程池的各個組件。
-Executor/Executors
-ThreadPoolExecutor使用介紹
-ThreadPoolExecutor實現原理
–ThreadPoolExecutor的中斷與優雅關閉 shutdown + awaitTermination
–shutdown的一個誤區 Executor/Executors
Executor是線程池架構最基本的幾個介面:
public interface Executor { void execute(Runnable command);}
而Executors是線程池架構的一個工具類,利用它可以方便的建立不同策略的線程池:
//單線程線程池:corePoolSize = maxPoolSize = 1, 隊列用的LinkedBlockingQueue public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
//固定數目的線程池:corePoolSize = maxPoolSize = n, 隊列用的LinkedBlockingQueue public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
//1。CachedThreadPool,corePoolSize = 0, 隊列為SynchronousQueue,maxPoolSize = Integer.MAX_VALUE(這也就意味著,每來一個任務,就建立一個線程。//2。關於SynchronousQueue,後面會單獨用一篇來分析。它是個特殊的隊列,沒本身沒有容量,放進去一個,就得等有線程拿出來,才能解除阻塞//3。從構造參數可以看出,空閑線程,60s沒人用,回收 public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
//單線程的,具有周期調度功能的線程池 public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1)); }//多線程的,具有周期調度功能的線程池 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS, new DelayedWorkQueue()); }
從上面可以看出,Executors的各個工具函數,都用的ThreadPoolExecutor/ScheduledThreadPoolExecutor這2個類,下面做詳細分析。 ThreadPoolExecutor ThreadPoolExecutor建構函式詳解
下面是ThreadPoolExecutor的參數最全的建構函式,搞清楚了每個參數的含義,也就明白了線程池的各種不同策略,也就明白了上述Executors工具類中的各個工具函數。
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.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
corePoolSize: 線程池始終維護的線程個數
maxPoolSize: corePooSize滿了,隊列也滿的情況下,擴充線程至這個值
keepAliveTime/TimeUnit: maxPoolSize中的空閑線程,過多長時間銷毀,匯流排程數收縮回corePoolSize
blockingQueue: 線程池所用的隊列類型
threadFactory: 線程建立工廠,可以自訂,也有一個預設的
RejectedExecutionHandler: corePoolSize滿了,隊列滿了,maxPoolSize滿了,最後的拒絕策略。 ThreadPool任務處理流程
從上述建構函式解釋,可以看出每次submit的任務,有如下的處理流程:
step1: 判斷當前線程數 >= corePoolSize。如果小於,建立線程執行;如果大於,進入step2
step2: 判斷隊列是否已滿。未滿,放入;已滿,進入step3
step3: 判斷當前線程數 >= maxPoolSize。如果小於,建立線程執行;如果大於,進入step4
step4: 根據拒絕策略,拒絕任務
總結一下:先判斷corePoolSize, 再判斷blockingQueue,再判斷maxPoolSize,最後使用拒絕策略 ThreadPool的4中拒絕策略
ThreadPoolExecutor的4個內部類,分別定義了4種策略。預設是AbortPolicy
//策略1:讓調用者直接在自己的線程裡面執行,線程池不做處理 public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }//策略2:線程池直接拋異常 public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } }//策略3:線程池直接把任務丟掉,當作什麼也沒發生 public static class DiscardPolicy implements RejectedExecutionHandler { public DiscardPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } }//策略4:把隊列裡面最老的任務刪除掉,把該任務放入隊列 public static class DiscardOldestPolicy implements RejectedExecutionHandler { public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } }
ThreadPoolExecutor實現原理
一般都知道,ThreadPool的基本實現原理就是一個隊列 + 一組worker線程,調用中不斷往隊列中放,worker線程不斷去取。但在具體實現中,有不同的實現策略:
策略1: 阻塞隊列 vs. 非阻塞隊列
在ThreadPoolExecutor中,使用的是阻塞隊列,即如下的BlockingQueue介面:
private final BlockingQueue<Runnable> workQueue;
這也就意味著,worker內部不需要自己設定wait/notify機制,它只管從隊列中取,取的到執行,取不到,自動會阻塞。
也有使用非阻塞隊列的,比如Tomcat 6裡面的線程池實現(以後會源碼詳細分析),當沒有請求處理時,worker內部自己實現阻塞,然後又新的請求進來,再通知woker。
策略2:新來的請求,是直接放入隊列,還是先new一個新的thread。
ThreadPool的處理方式是優先new thread處理,thread count >= corePoolSize的時候,再考慮放入隊列。
策略3: 無界隊列 vs. 有界隊列。
如果無界隊列,意味著maxPoolSize的邏輯永遠不會執行。這在上面的Executors中,FixedThreadPool已有所體現。
除此之外,還有諸多實現上的細節,下面代碼詳細分析 源碼分析
//核心結構:一個BlockingQueue + 一個線程的Set + 一把鎖(控制對workers, 各種threadCount的互斥訪問)public class ThreadPoolExecutor extends AbstractExecutorService {。。。 private final BlockingQueue<Runnable> workQueue; private final ReentrantLock mainLock = new ReentrantLock(); private final HashSet<Worker> workers = new HashSet<Worker>();。。。}
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { //小於corePoolSize的判斷 if (runState == RUNNING && workQueue.offer(command)) { //入隊列 if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); //進入隊列之後,2次檢測 } else if (!addIfUnderMaximumPoolSize(command)) //小於maxPoolSize的判斷 reject(command); // 大於maxPoolSize,拒絕請求 } } //poolSize < corePoolSize的時候,直接new Thread,加入hashSet private boolean addIfUnderCorePoolSize(Runnable firstTask) { Thread t = null; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (poolSize < corePoolSize && runState == RUNNING) t = addThread(firstTask); } finally { mainLock.unlock(); } return t != null; } //隊列滿了,poolSize < maxPoolSize,再次new thread,加入hashSet private boolean addIfUnderMaximumPoolSize(Runnable firstTask) { Thread t = null; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (poolSize < maximumPoolSize && runState == RUNNING) t = addThread(firstTask); } finally { mainLock.unlock(); } return t != null; }
Worker的實現
private final class Worker implements Runnable { 。。。 private Runnable firstTask; //至所以有firstTask這個變數,是因為建立worker的時候,可以直接賦給它一個task執行;有可以不賦給task,讓它自己到blockingQueue裡面去迴圈取 Worker(Runnable firstTask) { this.firstTask = firstTask; } //1個死迴圈,不斷從blockingQueue中,取task執行。取不到,就會阻塞在getTask()裡面 public void run() { try { hasRun = true; Runnable task = firstTask; firstTask = null; while (task != null || (task = getTask()) != null) { runTask(task); task = null; } } finally { workerDone(this); //worker線程退出 } } 。。。 }//getTask裡面有個關鍵點:當poolSize <= corePoolSize時,是無限期阻塞下去,線程也就會一直存在,不會退出,死掉;當poolSize > corePoolSize或者允許coreThread也死去時,線程就只阻塞keepAliveTime的時間,時間到了,隊列還是空的,沒有請求,線程就退出,死掉了,同時poolSize--. Runnable getTask() { for (;;) { try { int state = runState; if (state > SHUTDOWN) return null; Runnable r; if (state == SHUTDOWN) r = workQueue.poll(); //poll是非阻塞調用,沒有直接返回null else if (poolSize > corePoolSize || allowCoreThreadTimeOut) r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS); //等待1個逾時時間,預設就是建構函式裡面傳進去的那個60s else r = workQueue.take(); //take是阻塞調用,沒有,一直阻塞 if (r != null) return r; if (workerCanExit()) { if (runState >= SHUTDOWN) interruptIdleWorkers(); return null; } } catch (InterruptedException ie) { // On interruption, re-check runState } } }
中斷與優雅關閉
線程池狀態切換圖
volatile int runState; static final int RUNNING = 0; static final int SHUTDOWN = 1; static final int STOP = 2; static final int TERMINATED = 3;
初始處於RUNNING狀態,當調用shutdown()之後,切換到SHUTDOWN狀態;調用shutdownNow(),切換到STOP狀態。
那shutdown與shutdownNow有什麼區別嗎。
shutdown(): 不會清空隊列裡面的任務,會等所有任務執行完畢。並且它只會中斷那些 > corePoolSize的idle線程
shutdownNow(): 清空隊列裡面所有任務,同時向所有線程發送中斷訊號
當隊列為空白 && pool也為空白時,線程池進入Terminated狀態。 shutdown/shutdownNow源碼解析
public void shutdown() { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkPermission(shutdownPerm); //許可權檢查,check當前調用者,是否有許可權關閉線程池。沒有許可權,拋出異常。 final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (security != null) { for (Worker w : workers) security.checkAccess(w.thread); //許可權檢查 } int state = runState; if (state < SHUTDOWN) runState = SHUTDOWN; //從running切換到shutdown。不能從stop或者terminated切換到shutdown try { for (Worker w : workers) { w.interruptIfIdle(); //遍曆所有線程,向其發送訊號 } } catch (SecurityException se) { runState = state; throw se; } tryTerminate(); //試圖終止線程池 } finally { mainLock.unlock(); } } public List<Runnable> shutdownNow() { SecurityManager security = System.getSecurityManager(); if (security != null) security.checkPermission(shutdownPerm); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (security != null) { for (Worker w : workers) security.checkAccess(w.thread); } int state = runState; if (state < STOP) runState = STOP; //切換到stop狀態 try { for (Worker w : workers) { w.interruptNow(); //變數所有線程,發中斷訊號,不管是否正在執行任務 } } catch (SecurityException se) { // Try to back out runState = state; // tryTerminate() here would be a no-op throw se; } List<Runnable> tasks = drainQueue(); //清空隊列請求 tryTerminate(); // 試圖終止線程池 return tasks; } finally { mainLock.unlock(); } }
從上面,可以看出,shutdown和shutdownNow的區別有3點:
(1)一個是切換到shutdown狀態,一個是切換到stop狀態
(2)遍曆所有線程,一個調用的interruptIfIdle, 一個調用的interruptNow。
(3)shutdownNow會清空隊列中的任務
那interruptIfIdle和interruptNow有什麼區別呢。
private final class Worker implements Runnable { 。。。 private final ReentrantLock runLock = new ReentrantLock(); void interruptIfIdle() { final ReentrantLock runLock = this.runLock; if (runLock.tryLock()) { try { if (hasRun && thread != Thread.currentThread()) thread.interrupt(); } finally { runLock.unlock(); } } } void interruptNow() { if (hasRun) thread.interrupt(); } public void run() { try { hasRun = true; Runnable task = firstTask; firstTask = null; while (task != null || (task = getTask()) != null) { //getTask內部,也有響應中斷的邏輯 runTask(task); task = null; } } finally { workerDone(this); } } //每次從隊列中拿出一個任務,執行之前,會加鎖 private void runTask(Runnable task) { final ReentrantLock runLock = this.runLock; runLock.lock(); try { if ((runState >= STOP || (Thread.interrupted() && runState >= STOP)) && hasRun) thread.interrupt(); boolean ran = false; beforeExecute(thread, task); try { task.run(); ran = true; afterExecute(task, null); ++completedTasks; } catch (RuntimeException ex) { if (!ran) afterExecute(task, ex); throw ex; } } finally { runLock.unlock(); } }
可以看出,interruptIfIdle和interuptNow的關鍵區別是:前者會加鎖訪問,這也就意味著,如果被中斷的線程,正在執行runTask,則鎖是拿不到的。此時shutdown會阻塞,直到woker執行完runTask。 shutdown的一個誤區
根據上面分析,是不是shutdown一定會阻塞到隊列中所有請求都執行完,再返回呢。或者說,shutdown返回的時候,是不是隊列裡面的請求就一定執行完了呢。
不一定。shutdown返回之後,線程池不一定立即關閉。為什麼呢。
請看下面的getTask函數
Runnable getTask() { for (;;) { try { int state = runState; if (state > SHUTDOWN) return null; Runnable r; if (state == SHUTDOWN) //如果線程池是shutdown狀態,就不阻塞了,不管是否能拿到,都是直接返回 r = workQueue.poll(); //關鍵點:如果是shutdown狀態,會一直迴圈,直到拿空隊列裡面所有任務 else if (poolSize > corePoolSize || allowCoreThreadTimeOut) r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS); else r = workQueue.take(); //case1: 別的線程先置了中斷標誌位,然後當前線程調用take //case 2: 先調用take阻塞在這,然後別的線程置了中斷標誌位 //2種case,都會拋出異常,進入下面的InterruptedException if (r != null) return r; if (workerCanExit()) { if (runState >= SHUTDOWN) // Wake up others interruptIdleWorkers(); return null; } // Else retry } catch (InterruptedException ie) { //阻塞的時候,收到中斷,不處理,再次迴圈檢查 // On interruption, re-check runState } } } public void run() { try { hasRun = true; Runnable task = firstTask; firstTask = null; while (task != null || (task = getTask()) != null) { //getTask內部,也有響應中斷的邏輯 runTask(task); task = null; } } finally { workerDone(this); } } //每次從隊列中拿出一個任務,執行之前,會加鎖 private void runTask(Runnable task) { final ReentrantLock runLock = this.runLock; runLock.lock(); try { if ((runState >= STOP || (Thread.interrupted() && runState >= STOP)) && hasRun) thread.interrupt(); boolean ran = false; beforeExecute(thread, task); try { task.run(); ran = true; afterExecute(task, null); ++completedTasks; }