思考這樣一個問題:
如果併發的線程數量很多,並且每個線程都是執行一個時間很短的任務就結束了,這樣頻繁創建線程就會大大降低系統的效率,因爲頻繁創建線程和銷燬線程需要時間。那麼有沒有一種辦法使得線程可以複用,就是執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務?
本節所要說的【線程池】就可以解決這種問題。那麼,什麼是線程池呢?
線程池是指在初始化一個多線程應用程序過程中創建一個線程集合,然後在需要執行新的任務時重用這些線程而不是新建一個線程。線程池中線程的數量通常完全取決於可用內存數量和應用程序的需求。然而,增加可用線程數量是可能的。線程池中的每個線程都有被分配一個任務,一旦任務已經完成了,線程回到池子中並等待下一次分配任務。
使用線程池的帶來的好處(原因);
- 線程池改進了一個應用程序的響應時間。由於線程池中的線程已經準備好且等待被分配任務,應用程序可以直接拿來使用而不用新建一個線程。
- 線程池節省了CLR 爲每個短生存週期任務創建一個完整的線程的開銷並可以在任務完成後回收資源。
- 線程池根據當前在系統中運行的進程來優化線程時間片。
- 線程池允許我們開啓多個任務而不用爲每個線程設置屬性。
- 線程池允許我們爲正在執行的任務的程序參數傳遞一個包含狀態信息的對象引用。
- 線程池可以用來解決處理一個特定請求最大線程數量限制問題。
1 ThreadPoolExecutor類
java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的類,下面剖心其源碼(提供了四個構造器):
public class ThreadPoolExecutor extends AbstractExecutorService { ..... public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable>workQueue); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable>workQueue,ThreadFactory threadFactory); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable>workQueue,RejectedExecutionHandler handler); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable>workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler); ... }
ThreadPoolExecutor類提供了四個構造函數,實際前三個構造函數都是調用第四個構造函數來初始化的。
參數解釋:
corePoolSize:核心池的大小;在創建了線程池後,默認情況下,線程池中並沒有任何線程,而是等待有任務到來才創建線程去執行任務,除非調用了prestartAllCoreThreads()或者prestartCoreThread()方法,從這2個方法的名字就可以看出,是預創建線程的意思,即在沒有任務到來之前就創建corePoolSize個線程或者一個線程;當線程池中的線程數目達到corePoolSize後,就會把到達的任務放到緩存隊列當中;
maximumPoolSize:線程池最大線程數;
keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。默認情況下,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會起作用,如果一個線程空閒的時間達到keepAliveTime,則會終止該線程,直到線程池中的線程數不大於corePoolSize;如果調用了allowCoreThreadTimeOut(boolean)方法,在線程池中的線程數不大於corePoolSize時,keepAliveTime參數也會起作用,直到線程池中的線程數爲0;
unit:參數keepAliveTime的時間單位,在TimeUnit類中有7種靜態屬性取值:
- TimeUnit.DAYS; //天
- TimeUnit.HOURS; //小時
- TimeUnit.MINUTES; //分鐘
- TimeUnit.SECONDS; //秒
- TimeUnit.MILLISECONDS; //毫秒
- TimeUnit.MICROSECONDS; //微妙
- TimeUnit.NANOSECONDS; //納秒
workQueue:一個阻塞隊列,用來存儲等待執行的任務,是一個比較重要的參數,一般有如下三種取值:
- ArrayBlockingQueue;
- LinkedBlockingQueue;
- SynchronousQueue;
一般選擇後兩種,並且線程池的排隊策略與BlockingQueue有關。
threadFactory:線程工廠,主要用來創建線程;
handler:表示當拒絕處理任務時的策略,有以下四種取值:
-
- ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
- ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不拋出異常。
- ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
- ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
ThreadPoolExecutor繼承了AbstractExecutorService,源碼爲:
public abstract class AbstractExecutorService implements ExecutorService { protected <T> RunnableFuture<T>newTaskFor(Runnable runnable, T value) { }; protected <T> RunnableFuture<T>newTaskFor(Callable<T> callable) { }; public Future<?> submit(Runnable task) {}; public <T> Future<T> submit(Runnable task, Tresult) { }; public <T> Future<T> submit(Callable<T>task) { }; private <T> TdoInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos) throws InterruptedException, ExecutionException,TimeoutException { }; public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException { }; public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,TimeoutException { }; public <T> List<Future<T>>invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException { }; public <T> List<Future<T>>invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException { }; }
AbstractExecutorService是一個抽象類,它實現了ExecutorService接口,ExecutorService源碼爲:
public interface ExecutorService extends Executor { void shutdown(); 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; }
而ExecutorService又是繼承了Executor接口,我們看一下Executor接口的實現:
public interface Executor { void execute(Runnablecommand); //執行傳進去的任務 }
ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor幾個之間的關係:
Executor是一個頂層接口,在它裏面只聲明瞭一個方法execute(Runnable),返回值爲void,參數爲Runnable類型,從字面意思可以理解,就是用來執行傳進去的任務的;
然後ExecutorService接口繼承了Executor接口,並聲明瞭一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象類AbstractExecutorService實現了ExecutorService接口,基本實現了ExecutorService中聲明的所有方法;
然後ThreadPoolExecutor繼承了類AbstractExecutorService。
- execute()
- submit()
- shutdown()
- shutdownNow()
*execute()方法 實際上是Executor中聲明的方法,在ThreadPoolExecutor進行了具體的實現,是核心方法,可以向線程池提交任務,交由線程池去執行。
*submit()方法 是ExecutorService中聲明的方法,在AbstractExecutorService有了具體的實現,但在ThreadPoolExecutor中並沒有重寫,和execute()方法不同,它既可以向線程池提交任務,又能夠返回任務執行的結果(去看submit()方法的實現,會發現它實際上還是調用的execute()方法,只不過它利用了Future來獲取任務執行結果)。
*shutdown()和shutdownNow()是用來關閉線程池的。
還有很多其他的方法:例如getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等獲取與線程池相關屬性的方法。
2 線程池的實現原理
線程池主要包括線程池狀態、任務執行、線程初始化、任務緩存隊列及排隊策略、拒絕策略、線程池的關閉和容量動態調整都幾個方面。
2.1 線程池狀態
ThreadPoolExecutor中定義幾個變量:
volatile int runState; static final int RUNNING = 0; static final int SHUTDOWN = 1; static final int STOP = 2; static final int TERMINATED = 3;
其中static final變量表示線程池狀態是 可見性volatile變量runState的可能取值。
當創建線程池後,線程池處於RUNNING狀態;
如果調用了shutdown()方法,則線程池處於SHUTDOWN狀態,此時線程池不能夠接受新的任務,它會等待所有任務執行完畢;
如果調用了shutdownNow()方法,則線程池處於STOP狀態,此時線程池不能接受新的任務,並且會去嘗試終止正在執行的任務;
當線程池處於SHUTDOWN或STOP狀態,並且所有工作線程已經銷燬,任務緩存隊列已經清空或執行結束後,線程池被設置爲TERMINATED狀態。
2.2 任務執行
先看一下ThreadPoolExecutor類比較重要成員變量:
private final BlockingQueue<Runnable>workQueue; //任務緩存隊列,用來存放等待執行的任務 private final ReentrantLockmainLock = new ReentrantLock(); //線程池狀態鎖,對線程池狀態(比如線程池大小和線程池狀態runState等)的改變都要使用這個鎖 private final HashSet<Worker>workers = new HashSet<Worker>();//用來存放工作集 private volatile long keepAliveTime; //線程存活時間 private volatile boolean allowCoreThreadTimeOut; //是否允許爲核心線程設置存活時間 private volatile int corePoolSize; //核心池的大小(即線程池中的線程數目大於這個參數時,提交的任務會被放進任務緩存隊列) private volatile int maximumPoolSize; //線程池最大能容忍的線程數,大於線程池大小;當任務繁重的時候,可能超出corPoolSize,但是小於maximumPollSize. private volatile int poolSize; //線程池中當前的線程數 private volatile RejectedExecutionHandlerhandler; //任務拒絕策略 private volatile ThreadFactorythreadFactory; //線程工廠,用來創建線程 private int largestPoolSize; //用來記錄線程池中曾經出現過的最大線程數 private long completedTaskCount; //用來記錄已經執行完畢的任務個數
任務從提交到最終執行完畢經歷了哪些過程?
上面說過,不管是execute()方法還是submit方法提交任務,最後都是通過執行execute()方法實現的,其源碼爲:
public void execute(Runnablecommand) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize ||!addIfUnderCorePoolSize(command)) { if (runState == RUNNING &&workQueue.offer(command)) { //防止在將此任務添加進任務緩存隊列的同時其他線程突然調用shutdown或者shutdownNow方法關閉線程池 if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); // 保證 添加到任務緩存隊列中的任務得到處理 } else if (!addIfUnderMaximumPoolSize(command)) reject(command); //is shutdown or saturated } }
代碼意思:首先,判斷提交的任務command是否爲null,若是null,則拋出空指針異。然後,如果線程池中當前線程數不小於核心池大小,那麼就會直接進入下面的if語句塊了。
如果線程池中當前線程數小於核心池大小,則接着執行後半部分addIfUnderCorePoolSize(command),如果執行完addIfUnderCorePoolSize這個方法返回false,則繼續執行下面的if語句塊,否則整個方法就直接執行完畢了。
語句塊內容:如果當前線程池處於RUNNING狀態,則將任務放入任務緩存隊列;如果當前線程池不處於RUNNING狀態或者任務放入緩存隊列失敗,則執行:addIfUnderMaximumPoolSize(command),如果執行失敗,則執行reject()方法進行任務拒絕處理。
private boolean addIfUnderCorePoolSize(Runnable firstTask) { Threadt = null; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (poolSize < corePoolSize && runState ==RUNNING) t= addThread(firstTask); //創建線程去執行firstTask任務 } finally { mainLock.unlock(); } if (t == null) return false; t.start(); return true; }
顧名思義:該方法的意圖就是當線程數量低於核心池大小時執行的方法
代碼大意:
首先,獲取鎖,通過if語句判斷當前線程池中的線程數目是否小於核心池大小和線程池狀態,前面已經判斷過了,此處再次判斷是爲了防止加鎖之前向線程池加入了新的線程導致超標和其他線程調用了shutdown或者shutdownNow方法關閉線程池。
其次,執行t= addThread(firstTask),穿進去執行任務的參數來創建一個線程返回來,如果t==null表示創建線程失敗,否則表示創建成功則調用t.start()方法啓動線程。
private Thread addThread(Runnable firstTask) { Workerw = new Worker(firstTask); Threadt = threadFactory.newThread(w); //創建一個線程,執行任務 if (t != null) { w.thread= t; //將創建的線程的引用賦值爲w的成員變量 workers.add(w); int nt = ++poolSize; //當前線程數加1 if (nt > largestPoolSize) largestPoolSize= nt; } return t; }
首先,用提交的任務創建了一個Worker對象,然後調用線程工廠threadFactory創建了一個新的線程;
然後,將線程t的引用賦值給了Worker對象的成員變量thread,接着通過workers.add(w)將Worker對象添加到工作集當中。
Worker類的實現:
private final class Worker implements Runnable { private final ReentrantLock runLock = new ReentrantLock(); private Runnable firstTask; volatile long completedTasks; Threadthread; Worker(RunnablefirstTask) { //傳進去了一個Runnable任務 this.firstTask= firstTask; } boolean isActive() { return runLock.isLocked(); } void interruptIfIdle() { final ReentrantLock runLock = this.runLock; if (runLock.tryLock()) { try { if (thread != Thread.currentThread()) thread.interrupt(); } finally { runLock.unlock(); } } } void interruptNow() { thread.interrupt(); } private void runTask(Runnabletask) { final ReentrantLock runLock = this.runLock; runLock.lock(); try { if (runState < STOP&& Thread.interrupted()&& runState>= STOP) boolean ran = false; beforeExecute(thread,task); //beforeExecute方法是ThreadPoolExecutor類的一個方法,沒有具體實現,用戶可以根據 //自己需要重載這個方法和後面的afterExecute方法來進行一些統計信息,比如某個任務的執行時間等 try { task.run(); ran= true; afterExecute(task, null); ++completedTasks; } catch (RuntimeException ex) { if (!ran) afterExecute(task,ex); throw ex; } } finally { runLock.unlock(); } } public void run() { //核心方法 try { Runnabletask = firstTask; firstTask= null; while (task != null || (task = getTask()) != null) { runTask(task); task= null; } } finally { workerDone(this); //當任務隊列中沒有任務時,進行清理工作 } } }
核心方法run():首先執行通過構造器傳進來的任務firstTask:調用runTask()執行,執行完任務之後,在while循環裏面不斷通過從緩存隊列裏getTask()去取新的任務來執行。
但是getTask是ThreadPoolExecutor類中的方法,並不是Worker類中的方法,下面是getTask方法的實現:
Runnable getTask() { for (;;) { try { int state = runState; if (state > SHUTDOWN) return null; Runnabler; if (state == SHUTDOWN) // Help drain queue r= workQueue.poll(); else if (poolSize >corePoolSize || allowCoreThreadTimeOut) //如果線程數大於核心池大小或者允許爲核心池線程設置空閒時間, //則通過poll取任務,若等待一定的時間取不到任務,則返回null r= workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS); else r= workQueue.take(); if (r != null) return r; if (workerCanExit()) { //如果沒取到任務,即r爲null,則判斷當前的worker是否可以退出 if (runState >= SHUTDOWN) // Wake up others interruptIdleWorkers(); //中斷處於空閒狀態的worker return null; } //Else retry } catch (InterruptedException ie) { //On interruption, re-check runState } } }
先判斷當前線程池狀態:
如果runState大於SHUTDOWN(即爲STOP或者TERMINATED),則直接返回null。
如果runState爲SHUTDOWN或者RUNNING,則從任務緩存隊列取任務。
如果當前線程池的線程數大於核心池大小corePoolSize或者允許爲核心池中的線程設置空閒存活時間,則調用poll(time,timeUnit)來取任務,這個方法會等待一定的時間,如果取不到任務就返回null。
然後判斷取到的任務r是否爲null,爲null則通過調用workerCanExit()方法來判斷當前worker是否可以退出
退出判斷方法:
private boolean workerCanExit() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); boolean canExit; //如果runState大於等於STOP,或者任務緩存隊列爲空了 //或者 允許爲核心池線程設置空閒存活時間並且線程池中的線程數目大於1 try { canExit= runState >= STOP || workQueue.isEmpty()|| (allowCoreThreadTimeOut&& poolSize> Math.max(1, corePoolSize)); } finally { mainLock.unlock(); } return canExit; }
如果線程池處於STOP狀態、或者任務隊列已爲空或者允許爲核心池線程設置空閒存活時間並且線程數大於1時,允許worker退出,然後調用interruptIdleWorkers()中斷處於空閒狀態的worker,中斷方法interruptIdleWorkers()的實現:
void interruptIdleWorkers() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) //實際上調用的是worker的interruptIfIdle()方法 w.interruptIfIdle(); } finally { mainLock.unlock(); } } void interruptIfIdle() { final ReentrantLock runLock = this.runLock; if (runLock.tryLock()){ //注意這裏,是調用tryLock()來獲取鎖的,因爲如果當前worker正在執行任務,鎖已經被獲取了,是無法獲取到鎖的 //如果成功獲取了鎖,說明當前worker處於空閒狀態 try { if (thread !=Thread.currentThread()) thread.interrupt(); } finally { runLock.unlock(); } } }
此處直接讓空閒線程去任務緩存隊列去取任務執行,而非把任務分派給線程,因爲這樣需要額外的管理,會增加難度和複雜度。
addIfUnderMaximumPoolSize方法的實現方法類似於addIfUnderCorePoolSize方法,唯一區別在於前者是的執行條件是:
線程池中的線程數達到了核心池大小並且往任務隊列中添加任務失敗:
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(); } if (t == null) return false; t.start(); return true; }
任務提交給線程池之後到被執行的整個過程:
- 如果當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會創建一個線程去執行這個任務;
- 如果當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(一般來說是任務緩存隊列已滿),則會嘗試創建新的線程去執行這個任務;
- 如果當前線程池中的線程數目達到maximumPoolSize,則會採取任務拒絕策略進行處理;
- 如果線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;如果允許爲核心池中的線程設置存活時間,那麼核心池中的線程空閒時間超過keepAliveTime,線程也會被終止。
2.3 初始化線程池中的線程
默認情況下,線程池創建後線程數爲0,提交任務之後纔會創建線程。
但是也可以提前預創建線程:兩種方式
· prestartCoreThread():初始化一個核心線程;
· prestartAllCoreThreads():初始化所有核心線程
public boolean prestartCoreThread() { return addIfUnderCorePoolSize(null); //傳進null,會阻塞在getTask方法中的r = workQueue.take(),等待任務隊列中有新任務。 } public int prestartAllCoreThreads() { int n = 0; while (addIfUnderCorePoolSize(null))//傳進參數null…… ++n; return n; }
2.4任務緩存隊列及排隊策略
任務緩存隊列---workQueue,類型爲BlockingQueue<Runnable>,用於存放等待的任務,通常有以下三種類型:
1)ArrayBlockingQueue:基於數組的先進先出隊列,創建時必指定大小;
2)LinkedBlockingQueue:基於鏈表的先進先出隊列,若創建時沒有指定大小,則默認爲Integer.MAX_VALUE;
3)synchronousQueue:特殊隊列,不保存任務而是新建線程來執行提交的任務。
2.5 任務拒絕策略
當線程池的任務緩存隊列已滿並且線程池中的線程數目達到maximumPoolSize時,如果仍有任務提交則採取任務拒絕策略,四種策略:
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:丟棄任務但不拋出異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
2.6 關閉線程池
ThreadPoolExecutor提供了兩個方法shutdown()和shutdownNow()用於關閉線程池:
shutdown():等任務緩存隊列中所有任務都執行完後終止線程池,不再接受新任務。
shutdownNow():打斷正在執行的任務立即終止線程池,並清空任務緩存隊列,返回未執行的任務
2.7 動態調整線程池容量
ThreadPoolExecutor提供了動態調整線程池容量大小的方法,通過調整線程池容量,可以立即創建新的線程執行任務:
1)setCorePoolSize():設置核心池大小
2)setMaximumPoolSize():設置線程池最大能創建的線程數目大小
3 線程池的具體應用
具體應用例子代碼:
public class Test { public static void main(String[] args) { ThreadPoolExecutorexecutor = new ThreadPoolExecutor(5, 10, 200,TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5)); for(int i=0;i<15;i++){ MyTaskmyTask = new MyTask(i); executor.execute(myTask); System.out.println("線程池中線程數目:"+executor.getPoolSize()+",隊列中等待執行的任務數目:"+ executor.getQueue().size()+",已執行玩別的任務數目:"+executor.getCompletedTaskCount()); } executor.shutdown(); } } class MyTask implements Runnable { private int taskNum; public MyTask(int num) { this.taskNum= num; } @Override public void run() { System.out.println("正在執行task "+taskNum); try { Thread.currentThread().sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("task"+taskNum+"執行完畢"); } }
執行結果:
正在執行task 0 線程池中線程數目:1,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0 線程池中線程數目:2,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0 正在執行task 1 線程池中線程數目:3,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0 正在執行task 2 線程池中線程數目:4,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0 正在執行task 3 線程池中線程數目:5,隊列中等待執行的任務數目:0,已執行玩別的任務數目:0 正在執行task 4 線程池中線程數目:5,隊列中等待執行的任務數目:1,已執行玩別的任務數目:0 線程池中線程數目:5,隊列中等待執行的任務數目:2,已執行玩別的任務數目:0 線程池中線程數目:5,隊列中等待執行的任務數目:3,已執行玩別的任務數目:0 線程池中線程數目:5,隊列中等待執行的任務數目:4,已執行玩別的任務數目:0 線程池中線程數目:5,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 線程池中線程數目:6,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 正在執行task 10 線程池中線程數目:7,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 正在執行task 11 線程池中線程數目:8,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 正在執行task 12 線程池中線程數目:9,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 正在執行task 13 線程池中線程數目:10,隊列中等待執行的任務數目:5,已執行玩別的任務數目:0 正在執行task 14 task 3執行完畢 task 0執行完畢 task 2執行完畢 task 1執行完畢 正在執行task 8 正在執行task 7 正在執行task 6 正在執行task 5 task 4執行完畢 task 10執行完畢 task 11執行完畢 task 13執行完畢 task 12執行完畢 正在執行task 9 task 14執行完畢 task 8執行完畢 task 5執行完畢 task 7執行完畢 task 6執行完畢 task 9執行完畢
分析其執行結果:
當線程池中線程數目大於5時,將任務放入任務緩存隊列;
當任務緩存隊列滿了之後,便創建新的線程。
如果將for循環中執行任務數量修改爲20,則會拋出任務拒絕異常。
但是,javadoc提倡使用Executors類中的幾個靜態方法來創建線程池:
public static ExecutorService newSingleThreadExecutor() { //創建容量爲1的緩衝池 return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newFixedThreadPool(int nThreads) { //創建固定容量大小的緩衝池 return new ThreadPoolExecutor(nThreads, nThreads, 0L,TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newCachedThreadPool() { //創建一個緩衝池,緩衝池容量大小爲Integer.MAX_VALUE return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L,TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
上述方法實際上也是調用了ThreadPoolExecutor,只不過參數都已配置:
newCachedThreadPool:使用SynchronousQueue,來任務時即時創建線程(線程空閒超過60秒則自動銷燬),corePoolSize=0;
corePoolSize和maximumPoolSize:使用LinkedBlockingQueue,corePoolSize=maximumPoolSize=1;
newFixedThreadPool:使用LinkedBlockingQueue,corePoolSize=maximumPoolSize;
4 線程池大小分配
4.1 一般參考方法
要想合理的配置線程池的大小,首先得分析任務的特性,可以從以下幾個角度分析:
- 任務的性質:CPU密集型任務、IO密集型任務、混合型任務。
- 任務的優先級:高、中、低。
- 任務的執行時間:長、中、短。
- 任務的依賴性:是否依賴其他系統資源,如數據庫連接等。
性質不同的任務可以交給不同規模的線程池執行。
CPU密集型任務應配置儘可能小的線程,可以設爲 1+NCPU
IO密集型任務應配置儘可能多的線程,因爲IO操作不佔用CPU,爲了提高CPU利用率,應加大線程數量,可以設置爲1+2*NCPU
對於混合型任務,如果可以拆分,則拆分成IO密集型和CPU密集型分別處理,前提是兩者運行的時間是差不多的,如果處理時間相差很大,則沒必要拆分了。
若任務對其他系統資源有依賴,例如,需要鏈接數據庫返回結果,等待時間越長,則CPU空閒的時間越長,那麼線程數量應設置得越大,才能更好的利用CPU。
當然具體合理線程池值大小,需要結合系統實際情況,在大量的嘗試下比較才能得出,以上只是人總結的規律。
4.2 線程池容量計算公式
下面是別人得到的一個具體的公式:
最佳線程數目 = ((線程等待時間+線程CPU時間)/線程CPU時間 )* CPU數目
化簡得到:
最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目
得出結論:線程等待時間所佔比例越高,需要越多線程;線程CPU時間所佔比例越高,需要越少線程。
4.3 線程池容量調整經驗
高併發、任務執行時間短的業務怎樣使用線程池?
高併發、任務執行時間長的業務怎樣使用線程池?
非高併發、任務執行時間長的業務怎樣使用線程池?
(1)高併發、任務執行時間短的業務,線程池線程數可以設置爲CPU核數+1,減少線程上下文的切換 。
(2)併發高、業務執行時間長,要從整體架構的設計來着手,看看這些業務裏面某些數據是否能做緩存是第一步,增加服務器是第二步,至於線程池的設 置,設置參考(3)。最後,業務執行時間長的問題,也可能需要分析一下,看看能不能使用中間件對任務進行拆分和解耦。
(3)併發不高、任務執行時間長的業務要區分開看:
a)假如是業務時間長集中在IO操作上,也就是IO密集型的任務,因爲IO操作並不佔用CPU,所以不要讓所有的CPU閒下來,可以適當加大線程池中的線程數目,讓CPU處理更多的業務
b)假如是業務時間長集中在計算操作上,也就是計算密集型任務,可以線程池中的線程數設置得少一些,減少線程上下文的切換 。