上篇文章講了下線程的建立及一些經常使用的方法,可是在使用的時候,大多數是採用了線程池來管理線程的建立,運行,銷燬等過程。本篇將着重講線程池的基礎內容,包括經過線程池建立線程,線程池的基本信息等。java
本小節全部代碼都是在CreateThreadByPool
類上,該類還有一個內部類MyThread
實現了Runnable
接口。
首先先把基本的代碼給寫出來ide
public class CreateThreadByPool { public static void main(String[] args) { } } class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " processing"); process(); System.out.println(Thread.currentThread().getName() + " end"); } private void process() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return String.format("MyThread{%s}", Thread.currentThread().getName()); } }
先來大概回顧一下,當咱們想建立10個線程的時候的代碼普通方式是怎樣的函數
private static void createThreadByNormalWay() { for (int i = 0; i < 10; i++) { MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start(); } }
在能看到的代碼中,是使用了start()
本身直接開啓了線程,可是若是用線程池方式來呢this
第一種建立線程池的方法是經過Executors
類的靜態方法來構建,經過這種方式總共能夠建立4種線程池。spa
而且能夠發現返回是ExecutorService
,因此還要接受返回值,最後經過execute
來啓動線程線程
private static void createThreadByPool() { ExecutorService executorService = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { MyThread myThread = new MyThread(); executorService.execute(myThread); } }
先無論底層是如何實現的,至少代碼上是把線程交給了線程池來執行,這樣可以保證線程可以統一管理。code
簡單的比喻就是前者是要你本身去找班長簽到,後者是班長統一管理這整個班的簽到。在main函數中調用看看普通方法和經過線程池建立的線程有什麼區別orm
能夠很明顯的看到有如下幾點區別繼承
除了使用Executors.newFixedThreadPool()
建立線程池,還能夠經過new ThreadPoolExecutor()
,這裏可能有的小夥伴會迷糊了,怎麼上面放回的類是ExecutorService
,如今返回的又是ThreadPoolExecutor
,其實二者是同一個東西。接口
能夠看到ThreadExecutorPool
是繼承了 AbstractExecutorService
,然後者是實現了ExecutorService
。經過該方法建立的線程池的代碼以下
能夠先這樣運行體驗下,至於說構造函數裏面不一樣參數的含義,在後面的篇幅中會說到,到時候再返回來看便可。
private static void createThreadByThreadPoolExecutor() { ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); for (int i = 0; i < 10; i++) { MyThread myThread = new MyThread(); executor.execute(myThread); } }
看下運行結果
輸出結果沒啥好講的,可是若是細心的小夥伴在上一個gif就會發現,經過線程池來啓動線程的方式,程序並無退出,會一直運行。這是由於咱們沒有shutdown
線程池。
回過頭來看看Executors.靜態方法
這種方法來建立線程池的源碼
能夠看到其實更深一層仍是使用了new ThreadPoolExecutor()
,只不過咱們本身能定製的構造函數的參數變得極其少,這時候確定有小夥伴疑問了,那爲何不直接都用new ThreadPoolExecutor()
呢?
《阿里java開發手冊》 嵩山版明確規定了兩點,一是線程資源必須經過線程池提供,不容許自行顯式建立線程;二是線程池不容許使用Executors去建立,而是經過ThreadPoolExecutor的方式去建立。
着重看第二點強制經過ThreadPoolExecutor的方式來建立線程,緣由在下面也有,來看看FixedThreadPool和SingleThreadPool的源碼
其它的無論,能夠看到二者調用構造函數中的隊列都是LinkedBlockingQueue
,這個隊列是無邊界的,因此有了容許請求長度爲Integer.MAX_VALUE
,會堆積大量的請求 ,從而致使OOM。
再來看看CachedThreadPool的源碼
注意這裏構造函數的第二個參數是線程池最大線程數,它設置成了Integer.MAX_VALUE
,這就可能會建立大量的線程,從而致使OOM。
上面也能夠看到,建立線程池最重要也是最應該使用的方法的是new ThreadPoolExecutor()
,接下來把重點放在ThreadPoolExecutor
這個類上面
這個是類中的全部的屬性,接下來再看看構造函數
有4種,可是歸根結底只有如下這一種構造函數,講下這些參數的意義,而後你們就能夠回頭看下上一小節的例子。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { //省略實現 }
corePoolSize
:核心線程數,大白話就是可以工做的線程數量 maximumPoolSize
:最大線程數,就是這個線程池能容納線程的數量keepAliveTime
:存活時間,當線程池中的線程數量大於核心線程數的時候,若是時候沒有任務提交,核心線程池外的線程不會當即被銷燬,而是會等待,直到等待的時間超過了這個字段纔會被回收銷燬unit
:存活時間的單位workQueue
:工做隊列,就是在線程開始被調用前,就是存在這個隊列中threadFactory
:線程工廠,執行程序建立新線程時使用的工廠handler
:拒絕策略,當達到線程邊界和隊列容量而採起的拒絕策略對於這個拒絕策略,簡單說下,有四種實現。
實現
RejectedExecutionHandler
接口就能實現本身的拒絕策略
下面就來簡單實現一個本身的拒絕策略,而且來看下上述類中屬性的信息
首先須要一個監控線程類
class MonitorThread implements Runnable { //注入一個線程池 private ThreadPoolExecutor executor; public MonitorThread(ThreadPoolExecutor executor) { this.executor = executor; } private boolean monitor = true; public void stopMonitor() { monitor = false; } @Override public void run() { //監控一直運行,每3s輸出一次狀態 while (monitor) { //主要邏輯是監控線程池的狀態 System.out.println( String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s, rejectedExecutionHandler: %s", this.executor.getPoolSize(), this.executor.getCorePoolSize(), this.executor.getActiveCount(), this.executor.getCompletedTaskCount(), this.executor.getTaskCount(), this.executor.isShutdown(), this.executor.isTerminated(), this.executor.getRejectedExecutionHandler())); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
同時實現自定義的拒絕策略
其實這仍是沒有對r處理,拒絕了就拒絕了,只是打印出來,可是並無實質性地處理
class MyRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("task is rejected"); } }
接下來就是public類TheradPoolInfo
,注意工做線程採用的是上一小節的MyThread
類
public class ThreadPoolInfo { public static void main(String[] args) throws InterruptedException { //新建了一個線程池,核心線程數是3,最大線程數是5,30s //隊列是ArrayBlockingQueue,而且大小邊界是3,拒絕策略自定義輸出一句話 ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3), new MyRejectedExecutionHandler()); //開啓監控線程 MonitorThread monitorThread = new MonitorThread(executor); new Thread(monitorThread).start(); //開啓工做線程 for (int i = 0; i < 10; i++) { executor.execute(new MyThread()); } //關閉線程池和監控線程 Thread.sleep(12000); executor.shutdown(); Thread.sleep(3000); monitorThread.stopMonitor(); } }
預期結果: 經過構造函數能夠知道,預期是有3個核心線程執行任務,會拒絕2個線程,完成8個任務(最大線程數是5,隊列長度是3,具體會在下一篇文章中講)。
能夠看到結果和預期的同樣
創做不易,若是對你有幫助,歡迎點贊,收藏和分享啦!
下面是我的公衆號,有興趣的能夠關注一下,說不定就是你的寶藏公衆號哦,基本2,3天1更技術文章!!!