第一:下降資源消耗。經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。html
第二:提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能當即執行。java
第三:提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配,調優和監控。數據庫
能夠先看下線程池的類圖:數組
線程池的狀態:服務器
A.線程池的建立dom
咱們能夠經過java.util.concurrent.ThreadPoolExecutor來建立一個線程池。ide
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);
建立線程池須要的參數介紹:源碼分析
B.向線程池提交任務this
提交任務有execute()和submit()兩個方法,下面看看他倆的區別:spa
①接收參數不一樣
execute()的參數是Runnable,submit()參數能夠是Runnable,也能夠是Cable。
②返回值不一樣
execute()沒有返回值,submit()有返回值Future。經過Future能夠獲取各個線程的完成狀況,是否有異常,還能試圖取消任務的執行。詳見》》》》》》》》
execute()很好理解,下面看個使用submit()獲取返回值的例子,假設我有不少更新各類數據的task,我但願若是其中一個task失敗,其它的task就不須要執行了。那我就須要catch Future.get拋出的異常,而後終止其它task的執行,代碼以下:
1 public class SubmitTest { 2 3 public static void main(String[] args) { 4 ExecutorService executorService = Executors.newCachedThreadPool(); 5 List<Future<String>> futureList = new ArrayList<>(); 6 // 建立10個任務並執行 7 for (int i = 0; i < 10; i++) { 8 // 使用ExecutorService執行Callable類型的任務,並將結果保存在future變量中 9 Future<String> future = executorService.submit(new TaskRunn(i)); 10 // 將任務執行結果存儲到List中 11 futureList.add(future); 12 } 13 // 正常關閉線程池 14 executorService.shutdown(); 15 // 遍歷任務的結果 16 for (Future<String> future : futureList) { 17 try { 18 System.out.println(future.get()); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } catch (ExecutionException e) { 22 // 出錯了中止全部的線程 23 executorService.shutdownNow(); 24 e.printStackTrace(); 25 return; 26 } 27 } 28 } 29 } 30 31 class TaskRunn implements Callable<String>{ 32 33 private int id; 34 public TaskRunn(int id) { 35 this.id = id; 36 } 37 38 /** 39 * 任務的具體過程,一旦任務傳給ExecutorService的submit方法,則該方法自動在一個線程上執行 40 */ 41 @Override 42 public String call() throws Exception { 43 System.out.println("call() begin..."+id+"//"+Thread.currentThread().getName()); 44 if (new Random().nextInt(10) > 5) { 45 throw new TaskException("task err:"+id+"//"+Thread.currentThread().getName()); 46 } 47 // 模擬業務耗時 48 for (int i = 0; i < 10; i++) { 49 Thread.sleep(1000); 50 } 51 return "result:"+id+"//" +Thread.currentThread().getName(); 52 } 53 } 54 55 // 定義本身的異常 56 class TaskException extends Exception{ 57 public TaskException(String mess) { 58 super(mess); 59 } 60 }
c.線程池的關閉
咱們能夠經過調用線程池的shutdown或shutdownNow方法來關閉線程池,它們的區別詳見 http://www.cnblogs.com/shamo89/p/6703563.html
能夠簡單的總結爲shutdown()是正常結束線程池,已經添加進去正在執行的線程正常執行,沒添加的線程不會再添加。shutdownNow()則是強制中斷線程池裏的線程,可是由於是經過interuppt()來執行的,因此會有侷限性,另外該方法會返回未執行的任務。
因此一般調shutdown來正常關閉線程池,若是任務不必定要執行完,則能夠調用shutdownNow。
A.流程分析:線程池的主要工做流程以下圖:
從上圖咱們能夠看出,當提交一個新任務到線程池時,線程池的處理流程以下:
B.源碼分析
上面的流程分析讓咱們很直觀的瞭解了線程池的工做原理,讓咱們再經過源代碼來看看是如何實現的。線程池執行任務的方法以下:
1 public void execute(Runnable command) { 2 if (command == null) 3 throw new NullPointerException(); 4 //若是線程數小於基本線程數,則建立線程並執行當前任務 5 if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { 6 //如線程數大於等於基本線程數或線程建立失敗,則將當前任務放到工做隊列中。 7 if (runState == RUNNING && workQueue.offer(command)) { 8 if (runState != RUNNING || poolSize == 0) 9 ensureQueuedTaskHandled(command); 10 } 11 //若是線程池不處於運行中或任務沒法放入隊列,而且當前線程數量小於最大容許的線程數量, 12 // 則建立一個線程執行任務。 13 else if (!addIfUnderMaximumPoolSize(command)) 14 //拋出RejectedExecutionException異常 15 reject(command); // is shutdown or saturated 16 } 17 }
C.工做線程
線程池建立線程時,會將線程封裝成工做線程Worker,Worker在執行完任務後,還會無限循環獲取工做隊列裏的任務來執行。咱們能夠從Worker的run方法裏看到這點:
1 public void run() { 2 try { 3 Runnable task = firstTask; 4 firstTask = null; 5 while (task != null || (task = getTask()) != null) { 6 runTask(task); 7 task = null; 8 } 9 } finally { 10 workerDone(this); 11 } 12 }
要想合理的配置線程池,就必須首先分析任務特性,能夠從如下幾個角度來進行分析:
任務性質不一樣的任務能夠用不一樣規模的線程池分開處理。
CPU密集型任務配置儘量小的線程,如配置Ncpu+1個線程的線程池。
IO密集型任務則因爲線程並非一直在執行任務,則配置儘量多的線程,如2*Ncpu。
混合型的任務,若是能夠拆分,則將其拆分紅一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐率要高於串行執行的吞吐率,若是這兩個任務執行時間相差太大,則不必進行分解。
咱們能夠經過Runtime.getRuntime().availableProcessors()方法得到當前設備的CPU個數。
優先級不一樣的任務可使用優先級隊列PriorityBlockingQueue來處理。它可讓優先級高的任務先獲得執行,須要注意的是若是一直有優先級高的任務提交到隊列裏,那麼優先級低的任務可能永遠不能執行。
執行時間不一樣的任務能夠交給不一樣規模的線程池來處理,或者也可使用優先級隊列,讓執行時間短的任務先執行。
依賴數據庫鏈接池的任務,由於線程提交SQL後須要等待數據庫返回結果,若是等待的時間越長CPU空閒時間就越長,那麼線程數應該設置越大,這樣才能更好的利用CPU。
建議使用有界隊列,有界隊列能增長系統的穩定性和預警能力,能夠根據須要設大一點,好比幾千。
有一次咱們組使用的後臺任務線程池的隊列和線程池全滿了,不斷的拋出拋棄任務的異常,經過排查發現是數據庫出現了問題,致使執行SQL變得很是緩慢,由於後臺任務線程池裏的任務全是須要向數據庫查詢和插入數據的,因此致使線程池裏的工做線程所有阻塞住,
任務積壓在線程池裏。
若是當時咱們設置成無界隊列,線程池的隊列就會愈來愈多,有可能會撐滿內存,致使整個系統不可用,而不僅是後臺任務出現問題。
固然咱們的系統全部的任務是用的單獨的服務器部署的,而咱們使用不一樣規模的線程池跑不一樣類型的任務,可是出現這樣問題時也會影響到其餘任務。
經過線程池提供的參數進行監控。線程池裏有一些屬性在監控線程池的時候可使用
經過擴展線程池進行監控。經過繼承線程池並重寫線程池的beforeExecute,afterExecute和terminated方法,咱們能夠在任務執行前,執行後和線程池關閉前幹一些事情。如監控任務的平均執行時間,最大執行時間和最小執行時間等。這幾個方法在線程池裏是空方法。如:
protected void beforeExecute(Thread t, Runnable r) { }