在介紹線程池以前先看看一道面試題:爲何要使用線程池?使用線程池的優點是什麼?java
做用:面試
線程池作的工做主要是控制運行的線程的數量,處理過程當中將任務放入隊列,而後再線程建立後啓動這些任務,若是線程數量超過了最大的數量的線程排隊等候,等其餘線程執行完畢,再從隊列中取出任務來執行。主要特定:線程複用,控制最大併發數,管理線程。在阿里巴巴java開發手冊中,使用線程池的好處是減小在建立和銷燬線程上所花的時間以及系統資源的開銷,解決資源不足的問題。若是不使用線程池,有可能形成系統建立大量同類線程而致使消耗完內存或者 「過分切換」的問題。緩存
優點:數據結構
java線程池概述:Java中的線程池是經過Executor框架實現的,該框架用到了Executor,Executors,ExecutorService,ThreadPoolExecutor這幾個類,以下圖所示: 多線程
Executors.newFixedThreadPool(int)併發
建立一個固定長度的線程池,不能夠擴容。能夠控制線程的最大併發數量,超出的線程會在隊列中等待。這種方式建立的線程池corePoolSize和maxinumPoolSize的值是相等的,它使用的是LinkedBlockingQueue。(注意: LinkedBlockingQueue默認大小是Integer.MAX_VALUE)框架
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, //LinkedBlockingQueue默認大小是Integer.MAX_VALUE new LinkedBlockingQueue<Runnable>()); }
Executors.newSingleThreadExecutor();this
建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序執行。這種方式建立建立的線程池將corePoolSize和maxinumPoolSize都設置爲1,它使用的是LinkedBlockingQueue。線程
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, //LinkedBlockingQueue默認大小是Integer.MAX_VALUE new LinkedBlockingQueue<Runnable>())); }
Executors.newCachedThreadPool();code
建立一個可緩存線程池,若是線程池長度超過處理須要,能夠靈活回收空閒線程,若無可回收,則建立新線程這種方式建立的線程池將corePoolSize設置爲0,將maxinumPoolSize設置爲Integer.MAX_VALUE,使用的是SynchronousQueue,也就是說來了任務就建立線程運行,當線程空閒超過60秒就銷燬線程。它底層使用的是SynchronousQueue
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
總結:上面三種不管哪一種方式建立線程池底層都是用了ThreadPoolExecutor,底層的數據結構都是阻塞隊列。
線程池的簡單使用,模擬10我的到5個窗口辦理業務。
public class MyThreadPool { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(5); try { for (int i = 0; i < 10; i++) { pool.submit(()->{ System.out.println(Thread.currentThread().getName()+"\t 處理業務"); }); } }catch (Exception e){ e.printStackTrace(); }finally { pool.shutdown(); } } }
線程池的建立源碼
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
在建立了線程池後,等待提交過來的任務請求
當調用execute()方法添加一個請求任務時候,線程池會作以下判斷
2.1 若是正在運行的線程數量<corePoolSize,立刻建立線程處理這個任務
2.2 若是正在運行的線程數量>=corePoolSize,就將這個任務加入隊列
2.3 若是這個時候隊列滿了並且正在運行線程數<maxinumPoolsize,就建立非核心線程馬上處理這個任務
2.4 若是隊列滿了,並且正在運行線程數量>=maxinumPoolsize,那麼線程就會啓動飽和拒絕策略來執行
當一個線程完成任務時候,它會從隊列中取出下一個任務執行
當一個線程空閒超過keepAliveTime是,線程池會判斷若是當前運行的線程數量大於corePoolSize,那麼這個線程會被銷燬,因此線程池的全部任務完成以後它最終會收縮到corePoolSize的大小。
所謂的拒絕策略就是線程池須要分配的線程數量大於線程池最大容量,沒法分配新的線程處理當前任務時候的策略。JDK默認提供瞭如下四種拒絕策略
AbortPolicy(默認):直接拋出RejectedExecutionExeception異常阻止系統運行
CallerRunsPolicy:「調用者運行」一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用者
DiscardOldestPolicy:拋棄隊列中等待最久的任務,而後把當前任務加入隊列中嘗試再次提交當前任務
DiscardPolicy:直接多餘丟棄任務,不予任何處理也不會拋出異常。若是容許任務丟失,這是最好的解決方案。
這四種拒絕策略均實現了RejectedExecutionHandler接口
因爲使用Executors建立的線程池workQueue隊列默認的長度是Integer.MAX_VALUE,這個數字太大,即便使用默認的拒絕策略也通常不會拋出相應的異常,反而可能形成OOM,因此阿里巴巴java技術手冊要求不能使用Executors的方式建立線程池,而是應該使用ThreadPoolExecutor的方式建立線程。
在自定義的線程池中maximumPoolSize=5,workQueue=3,因此該線程池可以接受的最大請求數爲8,而請求數倒是10,使用AbortPolicy拒絕策略超過8個請求就會拋出異常
public class MyThreadPool { public static void main(String[] args) { ExecutorService pool = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), //建立自定義線程池,使用AbortPolicy拒絕策略,請求線程超過8就會拋出異常 new ThreadPoolExecutor.AbortPolicy()); try { for (int i = 0; i < 10; i++) { int temp = i; pool.submit(()->{ System.out.println(Thread.currentThread().getName()+"\t 處理業務" + temp); }); } }catch (Exception e){ e.printStackTrace(); }finally { pool.shutdown(); } } } pool-1-thread-1 處理業務0 java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@b4c966a rejected from java.util.concurrent.ThreadPoolExecutor@2f4d3709[Running, pool size = 5, active threads = 5, queued tasks = 3, completed tasks = 0] pool-1-thread-2 處理業務1 pool-1-thread-1 處理業務3 at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047) pool-1-thread-2 處理業務4 pool-1-thread-2 處理業務5 at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823) pool-1-thread-3 處理業務2 at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369) pool-1-thread-4 處理業務6 at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112) pool-1-thread-5 處理業務7 at com.ThreadPool.MyThreadPool.main(MyThreadPool.java:26)
「調用者運行」一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用者。在本例中超出的兩個請求會被交還給main線程處理
//將上述代碼建立線程池的拒絕策略改變,其餘不變 ExecutorService pool = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); pool-1-thread-1 處理業務0 main 處理業務8 //請求數爲10,超過的兩個請求交給調用者main線程處理 main 處理業務9 pool-1-thread-2 處理業務1 pool-1-thread-2 處理業務3 pool-1-thread-2 處理業務4 pool-1-thread-2 處理業務5 pool-1-thread-4 處理業務6 pool-1-thread-3 處理業務2 pool-1-thread-5 處理業務7
拋棄隊列中等待最久的任務,而後把當前任務加入隊列中嘗試再次提交當前任務。
ExecutorService pool = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy()); pool-1-thread-1 處理業務0 pool-1-thread-1 處理業務5 pool-1-thread-1 處理業務8 pool-1-thread-1 處理業務9 pool-1-thread-2 處理業務1 pool-1-thread-3 處理業務2 pool-1-thread-4 處理業務6 pool-1-thread-5 處理業務7 執行結果中3和4請求被丟棄,由此可知該隊列中3和4請求時等待時間最久的
直接多餘丟棄任務,不予任何處理也不會拋出異常。
ExecutorService pool = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy()); pool-1-thread-2 處理業務1 pool-1-thread-2 處理業務3 pool-1-thread-2 處理業務4 pool-1-thread-2 處理業務5 pool-1-thread-1 處理業務0 pool-1-thread-3 處理業務2 pool-1-thread-4 處理業務6 pool-1-thread-5 處理業務7 從執行結果來看請求8和請求9會被拋棄
如何合理配置線程池參數?
首先要獲取硬件參數,經過Runtime.getRuntime().availableProcessors()獲取CPU的核心數目
而後要區分所執行的任務是CPU密集型仍是IO密集型。
CPU密集型:該任務須要大量的運算,而沒有阻塞,CPU一直全速運行。 CPU密集任務只有在真正的多核CPU上纔可能獲得加速(經過多線程)。而在單核CPU上不管開幾個模擬的多線程,該任務都不可能獲得加速,由於CPU總的運算能力就是那麼多。CPU密集型任務配置儘量少的線程數量:通常公式:CPU核數+1線程的線程池
IO密集型:執行該任務須要大量的IO,即大量的阻塞。在單線程上運行IO密集型的任務會致使浪費大量的CPU運算能力在等待上。因此在IO密集型任務中使用多線程能夠大大的加速程序的運行,即便在單核CPU上,這種加速就是利用了被浪費掉的阻塞時間。IO密集型大部分線程被阻塞,故須要多配置線程數。通常有如下兩種配置策略
因爲IO密集型任務線程並非一直在執行任務,則應該配置儘量多的線程,如CPU核數*2
參考公式:CPU核數 / (1 - 阻塞係數)【阻塞係數在0.8~0.9之間】
好比8核CPU:線程池應配置8/(1-0.9)= 80個線程數。
阿里巴巴java開發手冊v1.2中對線程池中有如下兩點注意事項
【強制】線程資源必須經過線程池提供,不容許在應用中自行顯式建立線程。
說明:使用線程池的好處是減小在建立和銷燬線程上所花的時間以及系統資源的開銷,解決資源不足的問題。若是不使用線程池,有可能形成系統建立大量同類線程而致使消耗完內存或者 「過分切換」的問題。
【強制】線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors 返回的線程池對象的弊端以下:
FixedThreadPool 和 SingleThreadPool: 容許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而致使 OOM。
CachedThreadPool 和 ScheduledThreadPool: 容許的建立線程數量爲 Integer.MAX_VALUE,可能會建立大量的線程,從而致使 OOM。