什麼是池化技術?簡單來講就是優化資源的使用,我準備好了一些資源,有人要用就到我這裏拿,用完了就還給我。而一個比較重要的的實現就是線程池。那麼線程池用到了池化技術有什麼好處呢?java
也就是 線程複用、能夠控制最大併發數、管理線程數組
其實線程池我更願意說成四種封裝實現方式,一種原始實現方式。這四種封裝的實現方式都是依賴於最原始的的實現方式。因此這裏咱們先介紹四種封裝的實現方式併發
這個線程池頗有意思,說是線程池,可是池子裏面只有一條線程。若是線程由於異常而中止,會自動新建一個線程補充。
咱們能夠測試一下:
咱們對線程池執行十條打印任務,能夠發現它們用的都是同一條線程less
public static void test01() { ExecutorService threadPool = Executors.newSingleThreadExecutor(); try { //對線程進行執行十條打印任務 for(int i = 1; i <= 10; i++){ threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"=>執行完畢!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //用完線程池必定要記得關閉 threadPool.shutdown(); } }
pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢!
這個線程池是能夠指定咱們的線程池大小的,能夠針對咱們具體的業務和狀況來分配大小。它是建立一個核心線程數跟最大線程數相同的線程池,所以池中的線程數量既不會增長也不會變少,若是有空閒線程任務就會被執行,若是沒有就放入任務隊列,等待空閒線程。
咱們一樣來測試一下:性能
public static void test02() { ExecutorService threadPool = Executors.newFixedThreadPool(5); try { //對線程進行執行十條打印任務 for(int i = 1; i <= 10; i++){ threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"=>執行完畢!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //用完線程池必定要記得關閉 threadPool.shutdown(); } }
咱們建立了五條線程的線程池,在打印任務的時候,能夠發現線程都有進行工做測試
pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-2=>執行完畢! pool-1-thread-3=>執行完畢! pool-1-thread-5=>執行完畢! pool-1-thread-4=>執行完畢!
這個線程池是建立一個核心線程數爲0,最大線程爲Inter.MAX_VALUE的線程池,也就是說沒有限制,線程池中的線程數量不肯定,但若是有空閒線程能夠複用,則優先使用,若是沒有空閒線程,則建立新線程處理任務,處理完放入線程池。
咱們一樣來測試一下
優化
建立一個沒有最大線程數限制的能夠定時執行線程池
在這裏,還有建立一個只有單個線程的能夠定時執行線程池(Executors.newSingleThreadScheduledExecutor())這些都是上面的線程池擴展開來了,不詳細介紹了。this
上面咱們也說到了線程池有五種實現方式,可是實際上咱們就介紹了四種。那麼最後一種是什麼呢?不急,咱們能夠點開咱們上面線程池實現方式的源碼進行查看,能夠發現線程
而點開其餘幾個線程池到最後均可以發現,他們實際上用的就是這個ThreadPoolExecutor
。咱們把源代碼粘過來分析,其實也就是這七大參數指針
/** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param threadFactory the factory to use when the executor * creates a new thread * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code threadFactory} or {@code handler} is null */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
毫無懸念,這就是最後一種方式,也是其餘實現方式的基礎。而用這種方式也是最容易控制,由於咱們能夠自由的設置參數。在阿里巴巴開發手冊中也提到了
因此咱們更須要去了解這七大參數,在平時用線程池的時候儘可能去用ThreadPoolExecutor
。而關於這七大參數咱們簡單歸納就是
Runtime.getRuntime().availableProcessors();
獲取,而後設置。這裏咱們用一個例子能夠更好理解這些參數在線程池裏面的位置和做用。
如圖1.0,咱們這是一個銀行
咱們一共有五個櫃檯,能夠理解爲線程池的最大線程數量,而其中有兩個是在營業中,能夠理解爲線程池核心線程個數。而下面的等待廳能夠理解爲用於保存等待執行任務的阻塞隊列。銀行就是建立線程的工廠。
而關於空閒線程存活時間,咱們能夠理解爲如圖1.1這種狀況,當五個營業中,卻只有兩我的須要被服務,而其餘三我的一直處於等待的狀況下,等了一個小時了,他們被通知下班了。這一個小時時間就能夠說是空閒線程存活時間,而存活時間單位,顧名思義。
到如今咱們就剩一個拒絕策略還沒介紹,什麼是拒絕策略呢?咱們能夠假設當銀行五個櫃檯都有人在被服務,如圖1.2。而等待廳這個時候也是充滿了人,銀行實在容不下人了。
這個時候對銀行外面那個等待的人的處理策略就是拒絕策略。
咱們一樣瞭解以後用代碼來測試一下:
public static void test05(){ ExecutorService threadPool = new ThreadPoolExecutor( //核心線程數量 2, //最大線程數量 5, //空閒線程存活時間 3, //存活單位 TimeUnit.SECONDS, //這裏咱們使用大多數線程池都默認使用的阻塞隊列,並使容量爲3 new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), //咱們使用默認的線程池都默認用的拒絕策略 new ThreadPoolExecutor.AbortPolicy() ); try { //對線程進行執行十條打印任務 for(int i = 1; i <= 2; i++){ threadPool.execute(()->{ System.out.println(Thread.currentThread().getName()+"=>執行完畢!"); }); } } catch (Exception e) { e.printStackTrace(); } finally { //用完線程池必定要記得關閉 threadPool.shutdown(); } }
咱們執行打印兩條任務,能夠發現線程池只用到了咱們的核心兩條線程,至關於只有兩我的須要被服務,因此咱們就開了兩個櫃檯。
pool-1-thread-1=>執行完畢! pool-1-thread-2=>執行完畢!
可是在咱們將打印任務改到大於5的時候,(咱們改爲8)咱們能夠發現線程池的五條線程都在使用了,人太多了,咱們的銀行須要都開放了來服務。
for(int i = 1; i <= 8; i++)
pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-2=>執行完畢! pool-1-thread-3=>執行完畢! pool-1-thread-4=>執行完畢! pool-1-thread-5=>執行完畢!
在咱們改爲大於8的時候,能夠發現拒絕策略觸發了。銀行實在容納不下了,因此咱們把外面那我的用策略打發了。
for(int i = 1; i <= 9; i++)
在這裏咱們也能夠得出一個結論:
線程池大小= 最大線程數 + 阻塞隊列大小
在上面咱們在使用的阻塞隊列是大多數的線程池都使用的阻塞隊列,因此就引起思考下面這個問題。
咱們在使用ThreadPoolExecutor的時候是能夠本身選擇拒絕策略的,而拒絕策略咱們所知道的有四種。
咱們在上面使用的就是AbortPolicy拒絕策略,在執行打印任務超出線程池大小的時候,拋出了異常。
咱們將拒絕策略修改成CallerRunsPolicy,執行後能夠發現,由於第九個打印任務被拒絕了,因此它被調用者所在的線程執行了,也就是咱們的main線程。(由於它從main線程來的,如今又回到了main線程。因此咱們說它從哪裏來回哪裏去)
ExecutorService threadPool = new ThreadPoolExecutor( //核心線程數量 2, //最大線程數量 5, //空閒線程存活時間 3, //存活單位 TimeUnit.SECONDS, //這裏咱們使用大多數線程池都默認使用的阻塞隊列,並使容量爲3 new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), //咱們使用默認的線程池都默認用的拒絕策略 new ThreadPoolExecutor.CallerRunsPolicy() );
pool-1-thread-2=>執行完畢! main=>執行完畢! pool-1-thread-2=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-2=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-3=>執行完畢! pool-1-thread-4=>執行完畢! pool-1-thread-5=>執行完畢!
嘗試去競爭第一個任務,可是失敗了。這裏就沒顯示了,也不拋出異常。
pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-2=>執行完畢! pool-1-thread-3=>執行完畢! pool-1-thread-4=>執行完畢! pool-1-thread-5=>執行完畢!
多出來的任務,默默拋棄掉,也不拋出異常。
pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-1=>執行完畢! pool-1-thread-2=>執行完畢! pool-1-thread-3=>執行完畢! pool-1-thread-4=>執行完畢! pool-1-thread-5=>執行完畢!
能夠看到咱們的DiscardOldestPolicy與DiscardPolicy同樣的結果,可是它們實際上是不同,正如咱們最開始總結的那樣,DiscardOldestPolicy在多出的打印任務的時候會嘗試去競爭,而不是直接拋棄掉,可是很顯然競爭失敗否則也不會和DiscardPolicy同樣的執行結果。可是若是在線程比較多的時候就能夠很看出來。