深刻理解Java自帶的線程池和緩衝隊列

前言緩存

線程池是什麼服務器

線程池的概念是初始化線程池時在池中建立空閒的線程,一但有工做任務,可直接使用線程池中的線程進行執行工做任務,任務執行完成後又返回線程池中成爲空閒線程。使用線程池能夠減小線程的建立和銷燬,提升性能。多線程

舉個例子:我是一個包工頭,表明線程池,手底下有若干工人表明線程池中的線程。若是我沒接到項目,那麼工人就至關於線程池中的空閒線程,一但我接到了項目,我能夠馬上讓我手下的工人去工做,每一個工人同一時間執行只執行一個工做任務,執行完了就去函數

執行另外一個工做任務,知道沒有工做任務了,這時工人就能夠休息了(原諒我讓工人無休止的工做),也就是又變成了線程池中的空閒線程池。工具

隊列是什麼性能

隊列做爲一個緩衝的工具,當沒有足夠的線程去處理任務時,能夠將任務放進隊列中,以隊列先進先出的特性來執行工做任務測試

舉個例子,我又是一個包工頭,一開始我只接了一個小項目,因此只有三個工做任務,但我手底下有四個工人,那麼其中三人各領一個工做任務去執行就行了,剩下一我的就先休息。但忽然我又接到了幾個大項目,那麼有如今有不少工做任務了,但手底下的工人不夠啊。spa

那麼我有兩個選擇:操作系統

(1)僱傭更多的工人.net

(2)把工做任務記錄下來,按先來後到的順序執行

但僱傭更多等工人須要成本啊,對應到計算機就是資源的不足,因此我只能把工做任務先記錄下來,這樣就成了一個隊列了。

爲何要使用線程池

假設我又是一個包工頭,我如今手底下沒有工人了,但我接到了一個項目,有了工做任務要執行,那我確定要去找工人了,但招人成本是很高的,工做完成後還要給遣散費,這樣算起來好像不值,因此我事先僱傭了固定的幾個工人做爲個人長期員工,有工做任務就幹活,沒有就休息,若是工做任務實在太

多,那我也能夠再臨時僱傭幾個工人。一來二去工做效率高了,付出的成本也低了。Java自帶的線程池的原理也是如此。

Java自帶的線程池

Executor接口是Executor的父接口,基於生產者--消費者模式,提交任務的操做至關於生產者,執行任務的線程則至關於消費者,若是要在程序中實現一個生產者--消費者的設計,那麼最簡單的方式一般是使用Executor。

ExecutorService接口是對Executor接口的擴展,提供了對生命週期的支持,以及統計信息收集、應用程序管理機制和性能監視等機制。

經常使用的使用方法是調用Executors中的靜態方法來建立一個鏈接池。

(1)newFixedThreadPool

代碼演示:

 1 public class ThreadPoolTest {
 2     public static void main(String[] args){
 3         ExecutorService executor = Executors.newFixedThreadPool(3);
 4         for (int i = 0; i < 4; i++){
 5             Runnable runnable = new Runnable() {
 6                 public void run() {
 7                     CountDownLatch countDownLatch = new CountDownLatch(1); //計數器,用於阻塞線程
 8                     System.out.println(Thread.currentThread().getName() + "正在執行");
 9                     try {
10                         countDownLatch.await();
11                     } catch (InterruptedException e) {
12                         e.printStackTrace();
13                     }
14                 }
15             };
16             executor.execute(runnable);
17         }
18     }
19 }

測試結果:

pool-1-thread-1正在執行
pool-1-thread-3正在執行
pool-1-thread-2正在執行

newFixedThreadPool將建立一個固定長度的線程池,每當提交一個任務時就會建立一個線程,直到達線程池的最大數量,這時線程池的規模再也不變化(若是某個線程因爲發生了未預期的Exception而結束,那麼線程池會補充一個新的線程)。上述代碼中最大的線程數是3,但我提交了4個任務,並且每一個任務都阻塞住,因此前三個任務佔用了線程池全部的線程,那麼第四個任務永遠也不會執行,所以該線程池配套使用的隊列也是無界的。因此在使用該方法建立線程池時要根據實際狀況看須要執行的任務是否佔用過多時間,會不會影響後面任務的執行。

(2)newCachedThreadPool

測試代碼:

 1 public class ThreadPoolTest {
 2     public static void main(String[] args){
 3         ExecutorService executor = Executors.newCachedThreadPool();
 4         for (int i = 0; i < 4; i++){
 5             Runnable runnable = new Runnable() {
 6                 public void run() {
 7                     CountDownLatch countDownLatch = new CountDownLatch(1); //計數器,用於阻塞線程
 8                     System.out.println(Thread.currentThread().getName() + "正在執行");
 9                     try {
10                         countDownLatch.await();
11                     } catch (InterruptedException e) {
12                         e.printStackTrace();
13                     }
14                 }
15             };
16             executor.execute(runnable);
17         }
18     }
19 }

測試結果:

pool-1-thread-1正在執行
pool-1-thread-3正在執行
pool-1-thread-2正在執行
pool-1-thread-4正在執行

newCachedThreadPool將建立一個可緩存的線程池。若是線程池的當前規模超過了處理需求時,那麼就會回收部分空閒的線程(根據空閒時間來回收),當需求增長時,此線程池又能夠智能的添加新線程來處理任務。此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系統(或者說JVM)可以建立的最大線程大小。

(3)newSingleThreadExecutor

測試代碼:

 1 public class ThreadPoolTest {
 2     public static void main(String[] args){
 3         ExecutorService executor = Executors.newSingleThreadExecutor();
 4         for (int i = 0; i < 4; i++){
 5             final int index = i;
 6             Runnable runnable = new Runnable() {
 7                 public void run() {
 8                     System.out.println(Thread.currentThread().getName() + "正在執行工做任務--- >" + index);
 9                 }
10             };
11             executor.execute(runnable);
12         }
13     }
14 }

測試結果:

pool-1-thread-1正在執行工做任務--- >0
pool-1-thread-1正在執行工做任務--- >1
pool-1-thread-1正在執行工做任務--- >2
pool-1-thread-1正在執行工做任務--- >3

newSingleThreadExecutor是一個單線程的Executor,它建立單個工做者線程來執行任務,若是這個線程異常結束,會建立另外一個線程來代替。newSingleThreadExecutor能確保依照任務在隊列中的順序來串行執行。

(4)newScheduledThreadPool

測試代碼:

 1 public class ThreadPoolTest {
 2     public static void main(String[] args){
 3         ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
 4         for (int i = 0; i < 3; i++){
 5             final int index = i;
 6             Runnable runnable = new Runnable() {
 7                 public void run() {
 8                     System.out.println(Thread.currentThread().getName() + "延時1s後,每5s執行一次工做任務--- >" + index);
 9                 }
10             };
11             executor.scheduleAtFixedRate(runnable,1,5,TimeUnit.SECONDS);
12         }
13     }
14 }

測試結果:

pool-1-thread-1延時1s後,每5s執行一次工做任務--- >0
pool-1-thread-2延時1s後,每5s執行一次工做任務--- >1
pool-1-thread-3延時1s後,每5s執行一次工做任務--- >2
pool-1-thread-1延時1s後,每5s執行一次工做任務--- >0
pool-1-thread-3延時1s後,每5s執行一次工做任務--- >2
pool-1-thread-2延時1s後,每5s執行一次工做任務--- >1

newScheduledThreadPool建立了一個固定長度的線程池,並且以延遲或定時或週期的方式來執行任務,相似於Timer。可應用於重發機制。

以上四種建立線程池的方法其實都是調用如下這個方法,只是參數不同

corePoolSize  ---------------------> 核心線程數

maximumPoolSize ---------------> 最大線程數

keepAliveTime --------------------> 當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間

unit -----------------------------------> 時間單位

workQueue ------------------------> 用於存儲工做工人的隊列

threadFactory ---------------------> 建立線程的工廠

handler ------------------------------> 因爲超出線程範圍和隊列容量而使執行被阻塞時所使用的處理程序

經常使用的幾種隊列

(1)ArrayBlockingQueue:規定大小的BlockingQueue,其構造必須指定大小。其所含的對象是FIFO順序排序的。

(2)LinkedBlockingQueue:大小不固定的BlockingQueue,若其構造時指定大小,生成的BlockingQueue有大小限制,不指定大小,其大小有Integer.MAX_VALUE來決定。其所含的對象是FIFO順序排序的。

(3)PriorityBlockingQueue:相似於LinkedBlockingQueue,可是其所含對象的排序不是FIFO,而是依據對象的天然順序或者構造函數的Comparator決定。

(4)SynchronizedQueue:特殊的BlockingQueue,對其的操做必須是放和取交替完成。

排隊策略(如下排隊策略文字來自------->https://www.oschina.net/question/565065_86540

排隊有三種通用策略:

直接提交。工做隊列的默認選項是 SynchronousQueue,它將任務直接提交給線程而不保持它們。在此,若是不存在可用於當即運行任務的線程,則試圖把任務加入隊列將失敗,所以會構造一個新的線程。此策略能夠避免在處理可能具備內部依賴性的請求集時出現鎖。直接提交一般要求無界 maximumPoolSizes 以免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略容許無界線程具備增加的可能性。

無界隊列。使用無界隊列(例如,不具備預約義容量的 LinkedBlockingQueue)將致使在全部 corePoolSize 線程都忙時新任務在隊列中等待。這樣,建立的線程就不會超過 corePoolSize。(所以,maximumPoolSize的值也就無效了。)當每一個任務徹底獨立於其餘任務,即任務執行互不影響時,適合於使用無界隊列;例如,在 Web頁服務器中。這種排隊可用於處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略容許無界線程具備增加的可能性。

有界隊列。當使用有限的 maximumPoolSizes時,有界隊列(如 ArrayBlockingQueue)有助於防止資源耗盡,可是可能較難調整和控制。隊列大小和最大池大小可能須要相互折衷:使用大型隊列和小型池能夠最大限度地下降 CPU 使用率、操做系統資源和上下文切換開銷,可是可能致使人工下降吞吐量。若是任務頻繁阻塞(例如,若是它們是 I/O邊界),則系統可能爲超過您許可的更多線程安排時間。使用小型隊列一般要求較大的池大小,CPU使用率較高,可是可能遇到不可接受的調度開銷,這樣也會下降吞吐量。

咱們選其中的LinkedBlockingQueue隊列來解析

在上述Java自帶的建立線程池的方法中,newFixedThreadPool使用的隊列就是LinkedBlockingQueue

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

若是須要執行的工做任務少於核心線程數,那麼直接使用線程池的空閒線程執行任務,若是任務不斷增長,超過核心線程數,那麼任務將被放進隊列中,並且是沒有限制的,線程池中的線程也不會增長。

其餘線程池的工做隊列也是根據排隊的通用策略來進行工做,看客們能夠本身分析。

總結:

沒有建立鏈接池的方式,只有最適合的方式,使用Java自帶的方式建立或者本身建立鏈接池都是可行的,但都要依照自身的業務狀況選擇合適的方式。

若是你的工做任務的數量在不一樣時間差距很大,那麼若是使用newFixedThreadPool建立固定的線程就不合適,建立少了到時隊列裏會塞進太多的工做任務致使處理不及時,建立多了會致使工做任務少時有太多的線程處於空閒狀態形成資源浪費。

因此仍是須要根據實際狀況使用適合的建立方式。

相關文章
相關標籤/搜索