有點笨,參考了好幾篇大佬們寫的文章才整理出來的筆記....html
字面意思上解釋,線程池就是裝有線程的池,咱們能夠把要執行的多線程交給線程池來處理,和鏈接池的概念同樣,經過維護必定數量的線程池來達到多個線程的複用。java
通常咱們使用到多線程的編程的時候,須要經過new Thread(xxRunnable).start()
建立並開啓線程,咱們可使用多線程來達到最優效率(如多線程下載)。git
可是,線程不是越多就越好,線程過多,建立和銷燬就會消耗系統的資源,也不方便管理。github
除此以外,多線程還會形成併發問題,線程併發數量過多,搶佔系統資源從而致使阻塞。編程
咱們將線程放入線程池,由線程池對線程進行管理,能夠對線程池中緩衝的線程進行復用,這樣,就不會常常去建立和銷燬線程了,從而省下了系統的資源。segmentfault
線程池可以有效的控制線程併發的數量,可以解決多線程形成的併發問題。緩存
除此以外,線程池還可以對線程進行必定的管理,如延時執行、定時循環執行的策略等服務器
線程池的實現,主要是經過這個類ThreadPoolExecutor
,其的構造參數很是長,咱們先大概瞭解,以後再進行詳細的介紹。多線程
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue workQueue, RejectedExecutionHandler handler)
這裏大概簡單說明一下線程池的運行流程:併發
當線程被添加到線程池中,若是線程池中的當前的線程數量等於線程池定義的最大核心線程數量(corePoolSize)了,此線程就會別放入線程的工做隊列(workQueue)中,等待線程池的調用。
Java提供了一個工具類Excutors
,方便咱們快速建立線程池,其底層也是調用了ThreadPoolExecutor
不過阿里巴巴Java規範中強制要求咱們應該經過ThreadPoolExecutor
來建立本身的線程池,使用Excutors
容易形成OOM問題。
因此,咱們先從Excutors
開始學習,以後在對ThreadPoolExecutor
進行詳細的講解
因爲Excutors
是工具類,因此下面的介紹的都是其的靜態方法,若是是比較線程數目比較少的小項目,可使用此工具類來建立線程池
PS:把線程提交給線程池中,有兩種方法,一種是submit,另一種則是execute
二者的區別:
線程池能夠接收兩種的參數,一個爲Runnable對象,另外則是Callable對象
Callable是JDK1.5時加入的接口,做爲Runnable的一種補充,容許有返回值,容許拋出異常。
主要的幾個靜態方法:
方法 | 說明 |
---|---|
newFixedThreadPool(int nThreads) | 建立固定大小的線程池 |
newSingleThreadExecutor() | 建立只有一個線程的線程池 |
newCachedThreadPool() | 建立一個不限線程數上限的線程池,任何提交的任務都將當即執行 |
newScheduledThreadPool(int nThreads) | 建立一個支持定時、週期性或延時任務的限定線程數目的線程池 |
newSingleThreadScheduledExecutor() | 建立一個支持定時、週期性或延時任務的單個線程的線程池 |
建立一個單線程化的線程池,它只會用惟一的工做線程來執行任務,保證全部任務按照指定順序(FIFO, LIFO, 優先級)執行,咱們可使用它來達到控制線程順序執行。
控制進程順序執行:
Thread thread1 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("這是線程1"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("這是線程2"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread thread3 = new Thread(new Runnable() { @Override public void run() { try { System.out.println("這是線程3"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); //建立線程池對象 ExecutorService executorService = Executors.newSingleThreadExecutor(); //把線程添加到線程池中 executorService.submit(thread1); executorService.submit(thread2); executorService.submit(thread3);
以後出現的結果就是按照順序輸出
建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。定長線程池的大小最好根據系統資源進行設置。如Runtime.getRuntime().availableProcessors()
建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則新建線程,線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。
代碼:
//建立了一個自定義的線程 public class MyThread extends Thread { private int index; public MyThread(int index) { this.index = index; } @Override public void run() { System.out.println(index+" 當前線程"+Thread.currentThread().getName()); } } //建立緩存線程池 ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { executorService.execute(new MyThread(i)); try { //這裏模擬等待時間,等待線程池複用回收線程 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }
能夠看到結果都是使用的同一個線程
建立一個定長線程池,支持定時、週期性或延時任務執行
延遲1s後啓動線程:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); scheduledExecutorService.schedule(new MyThread(1),1, TimeUnit.SECONDS);
上面提到的那個構造方法其實只是ThreadPoolExecutor
類中的一個,ThreadPoolExecutor
類中存在有四種不一樣的構造方法,主要區別就是參數不一樣。
//五個參數的構造函數 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) //六個參數的構造函數-1 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) //六個參數的構造函數-2 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)
首先,有個概念須要明白,線程池的最大線程數(線程總數,maximumPoolSize)= 核心線程數(corePoolSize)+非核心線程數
核心線程和非核心線程有什麼區別呢?
核心線程是永遠不會被線程池丟棄回收(即便核心線程沒有工做),非核心線程則是超過必定時間(keepAliverTime)則就會被丟棄
當全部的核心線程都在工做時,新添加的任務會被添加到這個隊列中等待處理,若是隊列滿了,則新建非核心線程執行任務
1.SynchronousQueue:這個隊列接收到任務的時候,會直接提交給線程處理,而不保留它,若是全部線程都在工做怎麼辦?那就新建一個線程來處理這個任務!因此爲了保證不出現
線程數達到了maximumPoolSize而不能新建線程
的錯誤,使用這個類型隊列的時候,maximumPoolSize通常指定成Integer.MAX_VALUE,即無限大2.LinkedBlockingQueue:這個隊列接收到任務的時候,若是當前線程數小於核心線程數,則新建線程(核心線程)處理任務;若是當前線程數等於核心線程數,則進入隊列等待。因爲這個隊列沒有最大值限制,即全部超過核心線程數的任務都將被添加到隊列中,這也就致使了maximumPoolSize的設定失效,由於總線程數永遠不會超過corePoolSize
3.ArrayBlockingQueue:能夠限定隊列的長度,接收到任務的時候,若是沒有達到corePoolSize的值,則新建線程(核心線程)執行任務,若是達到了,則入隊等候,若是隊列已滿,則新建線程(非核心線程)執行任務,又若是總線程數到了maximumPoolSize,而且隊列也滿了,則發生錯誤
4.DelayQueue:隊列內元素必須實現Delayed接口,這就意味着你傳進去的任務必須先實現Delayed接口。這個隊列接收到任務時,首先先入隊,只有達到了指定的延時時間,纔會執行任務
拒絕策略 | 拒絕行爲 |
---|---|
AbortPolicy | 拋出RejectedExecutionException異常(默認) |
DiscardPolicy | 不處理,丟棄掉 |
DiscardOldestPolicy | 丟棄執行隊列中等待最久的一個任務,嘗試爲新來的任務騰出位置 |
CallerRunsPolicy | 直接由提交任務者執行這個任務 |
兩種方法設置拒絕策略:
//ThreadPoolExecutor對象的setRejectedExecutionHandler方法設置 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, queue); threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); //構造方法進行設置,省略
線程池默認的拒絕行爲是AbortPolicy,也就是拋出RejectedExecutionHandler異常,該異常是非受檢異常,很容易忘記捕獲。
若是不關心任務被拒絕的事件,能夠將拒絕策略設置成DiscardPolicy,這樣多餘的任務會悄悄的被忽略。
一個接口類,用來對線程進行設置,須要實現newThread(Runnable r)方法
官方的文檔說明:
newThread
此方法通常來初始化線程的優先級(priority),名字(name),守護進程(daemon)或線程組(ThreadGroup)
簡單的例子(讓某個類實現ThreadFactory接口):
@Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setDaemon(true); return thread; }
PS:把線程提交給線程池中,有兩種方法,一種是submit,另一種則是execute
二者的區別:
線程池能夠接收兩種的參數,一個爲Runnable對象,另外則是Callable對象
Callable是JDK1.5時加入的接口,做爲Runnable的一種補充,容許有返回值,容許拋出異常。
線程池的處理結果、以及處理過程當中的異常都被包裝到Future中,並在調用Future.get()方法時獲取,執行過程當中的異常會被包裝成ExecutionException,submit()方法自己不會傳遞結果和任務執行過程當中的異常。
獲取執行結果的代碼能夠這樣寫:
ExecutorService executorService = Executors.newFixedThreadPool(4); Future<Object> future = executorService.submit(new Callable<Object>() { @Override public Object call() throws Exception { //該異常會在調用Future.get()時傳遞給調用者 throw new RuntimeException("exception in call~"); } }); try { //得到返回結果 Object result = future.get(); } catch (InterruptedException e) { // interrupt } catch (ExecutionException e) { // exception in Callable.call() e.printStackTrace(); }
一個形象的比喻說明線程池的流程:
規定:
最開始的時候,公司是沒有一名員工。以後,公司接到了單子(任務),這個時候,公司纔去找員工(建立核心線程並讓線程開始執行),這個時候找到的員工就是正式員工了。
公司的聲譽愈來愈好,因而來了更多的單子,公司繼續招人,直到正式員工數量達到最大的正式員工的數量(核心線程數量已達到最大)
因而,多出來的單子就暫時地存放在了隊列中,都在排隊,等待正式員工們把手頭的工做作完以後,就從隊列中依次取出單子繼續工做。
某天,來了一個新單子,可是這個時候隊列已經滿了,公司爲了本身的信譽和聲譽着想,不得已只能去找臨時工(建立非核心線程)來幫忙開始進行工做(負責新單子)
在此以後,又來了新單子,公司繼續去招臨時工爲新來的單子工做,直到正式工和臨時工的數量已經達到了公司最大員工數。
這個時候,公司沒有辦法了,只能拒絕新來的單子了(拒絕策略)
此時,正式工和臨時工都是在加班加點去從隊列中取出任務來工做,終於某一天,隊列的已經沒有單子了,市場發展很差,單子愈來愈少,臨時工好久都不工做了(非核心線程超過了最大存活時間keepAliveTime),公司就把這些臨時工解僱了,直到剩下只有正式員工。
PS:若是也想要解僱正式員工(銷燬核心線程),能夠設置ThreadPoolExecutor
對象的的allowCoreThreadTimeOut
這個屬性爲true
我的理解,可能不是很正確,僅供參考!
方法 | 說明 |
---|---|
shutdown() | 再也不接受新的任務,以前提交的任務等執行結束再關閉線程池 |
shutdownNow() | 再也不接受新的任務,試圖中止池中的任務再關閉線程池,返回全部未處理的線程list列表。 |
若是是小的Java程序,可使用Excutors,若是是服務器程序,則使用ThreadPoolExecutor進行自定義線程池的建立
參考連接:
java中經常使用線程池的:newCachedThreadPool
Java線程池詳解
Java 線程池的認識和使用
Java 線程池全面解析
線程池,這一篇或許就夠了
Java線程池的運行原理以及使用詳解