1.線程池的好處。程序員
線程使應用可以更加充分合理的協調利用cpu 、內存、網絡、i/o等系統資源。數據庫
線程的建立須要開闢虛擬機棧,本地方法棧、程序計數器等線程私有的內存空間。編程
在線程的銷燬時須要回收這些系統資源。頻繁的建立和銷燬線程會浪費大量的系統資源,增長併發編程的風險。緩存
另外,在服務器負載過大的時候,如何讓新的線程等待或者友好的拒絕服務?這些丟失線程自身沒法解決的。因此須要經過線程池協調多個線程,並實現相似主次線程隔離、定時執行、週期執行等任務。線程池的做用包括:安全
在瞭解線程池的基本做用後,咱們學習一下線程池是如何建立線程的。首先從ThreadPoolExecutor構造方法講起,學習如何定義ThreadFectory和RejectExecutionHandler,並編寫一個最簡單的線程池示例。而後,經過分析ThreadPoolExecutor的execute和addWorker兩個核心方法,學習如何把任務線程加入到線程池中運行。ThreadPoolExecutor的構造方法以下:服務器
1 public ThreadPoolExecutor( 2 int corePoolSize, //第1個參數 3 int maximumPoolSize, //第2個參數 4 long keepAliveTime, //第3個參數 5 TimeUnit unit, //第4個參數 6 BlockingQueue<Runnable> workQueue, //第5個參數 7 ThreadFactory threadFactory, //第6個參數 8 RejectedExecutionHandler handler) { //第7個參數 9 if (corePoolSize < 0 || 10 maximumPoolSize <= 0 || 11 maximumPoolSize < corePoolSize || 12 keepAliveTime < 0) // 第一處 13 throw new IllegalArgumentException(); 14 if (workQueue == null || threadFactory == null || handler == null)//第二處 15 throw new NullPointerException(); 16 this.corePoolSize = corePoolSize; 17 this.maximumPoolSize = maximumPoolSize; 18 this.workQueue = workQueue; 19 this.keepAliveTime = unit.toNanos(keepAliveTime); 20 this.threadFactory = threadFactory; 21 this.handler = handler; 22 }
從代碼第2處來看,隊列、線程工程、拒絕處理服務都必須有實例對象,但在實際編碼中,不多有程序員對着三者進行實例化,而經過Executors這個線程池靜態工廠提供默認實現,那麼Executors與ThreadPoolExecutor 是什麼關係呢?線程池相關的類圖網絡
ExecutorService接口繼承了Executor接口,定義了管理線程任務的方法。ExecutorService 的抽象類AbstractExecutorService 提供了 submit()、invokeAll()等部分方法的實現,可是核心方法Executor.execute() 並無在這裏實現。由於全部的任務都在這個方法裏執行,不一樣的實現會帶來不一樣的執行策略,這一點在後續的ThreadPoolExecutor解析時,會一步步分析。經過Executor的靜態工廠方法能夠建立三個線程池的包裝對象:ForkJoinPool、ThreadPoolExecutor、ScheduledThreadPoolExecutor。Executors核心方法有5個:併發
1 /** 2 * Creates a work-stealing thread pool using all 3 * {@link Runtime#availableProcessors available processors} 4 * as its target parallelism level. 5 * @return the newly created thread pool 6 * @see #newWorkStealingPool(int) 7 * @since 1.8 8 */ 9 public static ExecutorService newWorkStealingPool() { 10 return new ForkJoinPool 11 (Runtime.getRuntime().availableProcessors(), 12 ForkJoinPool.defaultForkJoinWorkerThreadFactory, 13 null, true); 14 }
1 public static ExecutorService newFixedThreadPool(int nThreads) { 2 return new ThreadPoolExecutor(nThreads, nThreads, 3 0L, TimeUnit.MILLISECONDS, 4 new LinkedBlockingQueue<Runnable>()); 5 }
這裏,輸入的隊列沒有指明長度,下面介紹LinkedBlockingQueue的構造方法。ide
1 public LinkedBlockingQueue() { 2 this(Integer.MAX_VALUE); 3 }
使用這樣的無界隊列,若是瞬間請求很是大,會有OOM的風險。除newWorkStealingPool 外,其餘四個建立方式都存在資源耗盡的風險。學習
Executors 中默認的線程工程和拒絕策略過於簡單,一般對用戶不夠友好。線程工廠須要作建立前的準備工做,對線程池建立的線程必須明確標識,就像藥品的生產批號同樣,爲線程自己指定有意思的名稱和相應的序列號。拒絕策略應該考慮到業務場景返回相應的提示或者友好的跳轉 1 public class UserThreadFactory implements ThreadFactory { 2 private final String namePrefix;
3 private final AtomicInteger nextId = new AtomicInteger(); 4 //定義線程組名稱,在使用jstack 來排查問題是,很是有幫助 5 UserThreadFactory(String whatFeatureOfGroup){ 6 namePrefix = "UserThreadFactory's"+whatFeatureOfGroup+"-Worker-"; 7 } 8 9 @Override 10 public Thread newThread(Runnable task){ 11 String name = namePrefix+ nextId.getAndIncrement(); 12 Thread thread = new Thread(null,task,name,0);
//打印threadname 13 return thread; 14 } 15 16 public static void main(String[] args) { 17 UserThreadFactory threadFactory = new UserThreadFactory("你好"); 18 String ss = threadFactory.namePrefix; 19 Task task = new Task(ss); 20 Thread thread = null; 21 for(int i = 0; i < 10; i++) { 22 thread = threadFactory.newThread(task); 23 thread.start(); 24 } 25 26 } 27 } 28 29 class Task implements Runnable{ 30 String poolname; 31 Task(String poolname){ 32 this.poolname = poolname; 33 } 34 private final AtomicLong count = new AtomicLong(); 35 36 @Override 37 public void run(){ 38 System.out.println(poolname+"_running_"+ count.getAndIncrement()); 39 } 40 }
上述示例包括線程工廠和任務執行體的定義,經過newThread 方法快速、統一地建立線程任務,強調線程必定要是歐特定的意義和名稱,方便出錯時回溯。
下面簡單地實現一下RejectedExecutionHandler,實現了接口的rejectExecution方法,打印當前線程池狀態,源碼以下。
1 public class UserRejectHandler implements RejectedExecutionHandler { 2 3 @Override 4 public void rejectedExecution(Runnable task, ThreadPoolExecutor executor){ 5 System.out.println("task rejected."+ executor.toString()); 6 } 7 }
在ThreadPoolExecutor 中提供了4個公開的內部靜態類:
根據以前實現的線程工廠和拒絕策略,線程池的相關代碼實現以下:
public class UserThreadPool { public static void main(String[] args) { BlockingQueue queue = new LinkedBlockingQueue(2); UserThreadFactory f1 = new UserThreadFactory("第一機房"); UserThreadFactory f2 = new UserThreadFactory("第二機房"); UserRejectHandler handler = new UserRejectHandler(); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,2,60, TimeUnit.SECONDS,queue,f1,handler); ThreadPoolExecutor threadPoolExecutor2 = new ThreadPoolExecutor(1,2,60, TimeUnit.SECONDS,queue,f2,handler); Runnable task1 = new Task(""); for (int i = 0;i<200;i++){ threadPoolExecutor.execute(task1); threadPoolExecutor2.execute(task1); } } }
當任務被拒絕的時候,拒絕策略會打印出當前線程池的大小以及達到了maximumPoolSize=2 ,且隊列已滿。