天天看點

Java線程池工作原理 一、線程池的好處 二、線程池工作流程三、線程池參數四、阻塞隊列五、四種飽和政策六、向線程池送出任務七、關閉線程池八、線程池狀态九、調整線程池大小十、監控線程池

 Java中的線程池是運用場景最多的并發架構,幾乎所有需要異步或并發執行任務的程式都可以使用線程池,是以我們就要認識并弄懂線程池,以便于更好的為我們業務場景服務。

 一、線程池的好處 

在開發過程中,合理地使用線程池大緻有3個好處

第一:降低資源消耗。通過重複利用已建立的線程降低線程建立和銷毀造成的消耗。

第二:提高響應速度。當任務到達時,任務可以不需要等到線程建立就能立即執行。

第三:提高線程的可管理性。線程是稀缺資源,如果無限制地建立,不僅會消耗系統資源,

還會降低系統的穩定性,使用線程池可以進行統一配置設定、調優和監控。但是,要做到合理利用

線程池,必須對其實作原理了如指掌

二、線程池工作流程

1)當送出一個新任務到線程池時,線程池判斷corePoolSize線程池是否都在執行任務,如果有空閑線程,則從核心線程池中取一個線程來執行任務,直到目前線程數等于corePoolSize;

2)如果目前線程數為corePoolSize,繼續送出的任務被儲存到阻塞隊列中,等待被執行;

3)如果阻塞隊列滿了,那就建立新的線程執行目前任務,直到線程池中的線程數達到maxPoolSize,這時再有任務來,由飽和政策來處理送出的任務

三、線程池參數

下面是ThreadPoolExecutor類的構造方法傳參數 

pblic ThreadPoolExecutor(int corePoolSize, #核心線程數

                              int maximumPoolSize,   #最大線程數

                              long keepAliveTime, #達到最大線程數數時候,線程池的工作線程空閑後,保持存活的時間

                              TimeUnit unit,     #keepAliveTime機關

                              BlockingQueue<Runnable> workQueue #阻塞隊列

                              RejectedExecutionHandler handler  #飽和政策

) {

        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

             Executors.defaultThreadFactory(), handler);

    }

new ThreadPoolExecutor(6 ,12, 5L, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(10),new ThreadPoolExecutor.CallerRunsPolicy());

比如corePoolSize為6, maximumPoolSize為,12  keepAliveTime為5秒,隊列長度為10;送出任務數達到核心線程數6時候,新來的任務就會被放入LinkedBlockingQueue阻塞隊列,當隊列任務數達到10個時候,就會建立新線程執行任務,直到達到maximumPoolSize數量12;如果還有新來的任務,由政策來處理送出的任務;如果沒有,線程池空閑時候,超過5秒,建立的maximumPoolSize,就會被銷毀。

四、阻塞隊列

阻塞隊列BlockingQueue接口,從jdk1.5開始,有四個實作類,jdk8亦是如此

ArrayBlockingQueue:是一個基于數組結構的有界阻塞隊列,此隊列按FIFO(先進先出)原則對元素進行排序。

LinkedBlockingQueue:一個基于連結清單結構的阻塞隊列,此隊列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue,靜态工廠方法Executors.newFixedThreadPool()使用了這個隊列。

SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處于阻塞狀态,吞吐量通常要高于LinkedBlockingQueue,靜态工廠方法Executors.newCachedThreadPool使用了這個列。

PriorityBlockingQueue:一個具有優先級的無限阻塞隊列。

五、四種飽和政策

RejectedExecutionHandler(飽和政策):當隊列和線程池都滿了,說明線程池處于飽和狀态,那麼必須采取一種政策處理送出的新任務。這個政策預設情況下是AbortPolicy,表示無法處理新任務時抛出異常,檢視源碼,從jdk5開始RejectedExecutionHandler接口有四個實作類,jdk8亦是如此。

Java線程池工作原理 一、線程池的好處 二、線程池工作流程三、線程池參數四、阻塞隊列五、四種飽和政策六、向線程池送出任務七、關閉線程池八、線程池狀态九、調整線程池大小十、監控線程池

AbortPolicy:不處理,直接抛出異常。

CallerRunsPolicy:若線程池還沒關閉,調用目前所線上程來運作任務,r.run()執行。

DiscardOldestPolicy:LRU政策,丢棄隊列裡最近最久不使用的一個任務,并執行目前任務。

DiscardPolicy:不處理,丢棄掉,不抛出異常。

六、向線程池送出任務

可以使用兩個方法向線程池送出任務,分别為execute()和submit()方法

1、execute()方法用于送出不需要傳回值的任務,是以無法判斷任務是否被線程池執行成功

Java線程池工作原理 一、線程池的好處 二、線程池工作流程三、線程池參數四、阻塞隊列五、四種飽和政策六、向線程池送出任務七、關閉線程池八、線程池狀态九、調整線程池大小十、監控線程池

2、submit()方法用于送出需要傳回值的任務。

Java線程池工作原理 一、線程池的好處 二、線程池工作流程三、線程池參數四、阻塞隊列五、四種飽和政策六、向線程池送出任務七、關閉線程池八、線程池狀态九、調整線程池大小十、監控線程池

線程池會傳回一個future類型的對象,通過這個future對象可以判斷任務是否執行成功,并且可以通過future的get()方法來擷取傳回值,get()方法會阻塞目前線程直到任務完成,而使用get(long timeout,TimeUnit unit),在指定的時間内會等待任務執行,逾時則抛出逾時異常,等待時候會阻塞目前線程

七、關閉線程池

ThreadPoolExecutor提供了兩個方法,用于線程池的關閉,分别是shutdown()和shutdownNow(),它們的原理是周遊線

程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程

shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務

shutdownNow():立即終止線程池,并嘗試打斷正在執行的任務,并且清空任務緩存隊列,傳回尚未執行的任務

隻要調用了這兩個關閉方法中的任意一個,isShutdown方法就會傳回true。當所有的任務

都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會傳回true

是以,判斷線程池所有線程是否執行完成,可以這樣寫

while(true){//死循環
	if(threadPool.isTerminated()) {
			//執行自己的操作
			break;//true停止
	}
	Thread.sleep(500);//休眠500繼續循環
}
           

shutdown,隻是将線程池的狀态設定成SHUTDOWN狀态,然後中斷所有沒有正在執行任務的線

程,等待執行任務的線程完成。

Java線程池工作原理 一、線程池的好處 二、線程池工作流程三、線程池參數四、阻塞隊列五、四種飽和政策六、向線程池送出任務七、關閉線程池八、線程池狀态九、調整線程池大小十、監控線程池

shutdownNow首先将線程池的狀态設定成STOP,然後嘗試停止所有的正在執行或暫停任務的線程,

并傳回等待執行任務的清單

Java線程池工作原理 一、線程池的好處 二、線程池工作流程三、線程池參數四、阻塞隊列五、四種飽和政策六、向線程池送出任務七、關閉線程池八、線程池狀态九、調整線程池大小十、監控線程池

八、線程池狀态

線程池有五種運作狀态

1、RUNNING

(1) 狀态說明:線程池處在RUNNING狀态時,能夠接收新任務,以及對已添加的任務進行處理。 

(2) 狀态切換:線程池的初始化狀态是RUNNING。線程池被一旦被建立,就處于RUNNING狀态,且線程池中的任務數為0

2、 SHUTDOWN

(1) 狀态說明:線程池處在SHUTDOWN狀态時,不接收新任務,但能處理已添加的任務。 

(2) 狀态切換:調用線程池的shutdown()接口時,線程池由RUNNING -> SHUTDOWN。

3、STOP

(1) 狀态說明:線程池處在STOP狀态時,不接收新任務,不處理已添加的任務,并且會中斷正在處理的任務。 

(2) 狀态切換:調用線程池的shutdownNow()接口時,線程池由(RUNNING or SHUTDOWN ) -> STOP。

4、TIDYING

(1) 狀态說明:當所有的任務已終止,ctl記錄的”任務數量”為0,線程池會變為TIDYING狀态。當線程池變為TIDYING狀态時,會執行鈎子函數terminated()。terminated()在ThreadPoolExecutor類中是空的,若使用者想線上程池變為TIDYING時,進行相應的處理;可以通過重載terminated()函數來實作。 

(2) 狀态切換:當線程池在SHUTDOWN狀态下,阻塞隊列為空并且線程池中執行的任務也為空時,就會由 SHUTDOWN -> TIDYING。 當線程池在STOP狀态下,線程池中執行的任務為空時,就會由STOP -> TIDYING。

5、 TERMINATED

(1) 狀态說明:線程池徹底終止,就變成TERMINATED狀态。 

(2) 狀态切換:線程池處在TIDYING狀态時,執行完terminated()之後,就會由 TIDYING -> TERMINATED。

 源碼中可以看到

  private static final int COUNT_BITS = Integer.SIZE - 3;//32-3=29

 // runState is stored in the high-order bits  運作狀态存儲在高位

 //左移動29位,即-1的29次方,轉換為二進制11100000000000000000000000000000

   private static final int RUNNING    = -1 << COUNT_BITS;

  //即0的29次方,轉換為二進制00000000000000000000000000000000

    private static final int SHUTDOWN   =  0 << COUNT_BITS;

  //即1的29次方,轉換為二進制00100000000000000000000000000000

    private static final int STOP       =  1 << COUNT_BITS;

  //即2的29次方,轉換為二進制01000000000000000000000000000000

    private static final int TIDYING    =  2 << COUNT_BITS;

  //即3的29次方,轉換為二進制01100000000000000000000000000000

    private static final int TERMINATED =  3 << COUNT_BITS; 

合并線程池狀态與工作線程數量:ctlOf 

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));      

    //表示線程池最大容量,1的29次方減去1,即536870911,二進制表示00011111111111111111111111111111

    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    //rs線程池狀态,wc工作線程數量,兩者按位或,得到ctl ,高3位表示線程池狀态,低29位表示工作線程數量

    private static int ctlOf(int rs, int wc) { return rs | wc; } 

線程池狀态:runStateOf

private static int runStateOf(int c)     { return c & ~CAPACITY; }

描述:參數c=ctl.get(),get到合并值ctl,c & ~CAPACITY與運算,得到高3位就是線程狀态

釋義:CAPACITY   = (1 << COUNT_BITS) - 1的值536870911,"~"表示取反

536870911轉換二進制 00011111111111111111111111111111,取反後 11100000000000000000000000000000

參數c轉為二進制後,其高3位與111按位與,得到的高3位是參數c的高3位

CAPACITY取反的低29位為零,是以參數c與之低29位按位與,得到的低29位是CAPACITY取反的低29位,全為0

示例:

xxx0000000000000000000000001100  & 11100000000000000000000000000000

按位與後xxx000000000000000000000000000 即取參數c的高3位,CAPACITY的低29位全為0

所為參數c,即ctl,高3位表示線程池狀态

工作線程數量:workerCountOf 

 private static int workerCountOf(int c)  { return c & CAPACITY; }

描述:參數c=ctl.get(),get到合并值ctl ,c & CAPACITY運算,得到低29位,即線程池線程數量

釋義:CAPACITY   = (1 << COUNT_BITS) - 1的值536870911

536870911轉換二進制 00011111111111111111111111111111

參數c轉為二進制後,其高3位與000按位與後,得到的高3位為CAPACITY 的高3位,即000

CAPACIT的低29位都為1,是以參數c低29位與之按位與,得到的低29位總是參數c的低29位

示例:

010xxxxxxxxxxxxxxxxxxxxxxxxxxxx  & 00011111111111111111111111111111

按位與後000xxxxxxxxxxxxxxxxxxxxxxxxxxxx 即取CAPACITY得高3位000,參數c的低29位

所為參數c,即ctl,低29位表示線程數量

九、調整線程池大小

 ThreadPoolExecutor提供了setter方法,是以建立線程池之後,也可以動态調整線程池大小

Java線程池工作原理 一、線程池的好處 二、線程池工作流程三、線程池參數四、阻塞隊列五、四種飽和政策六、向線程池送出任務七、關閉線程池八、線程池狀态九、調整線程池大小十、監控線程池

十、監控線程池

可以通過線程池提供的參數進行監控,在監控線程池的時候可以使用以下屬性,擷取線程池任務狀況

taskCount:線程池需要執行的任務數量。

completedTaskCount:線程池在運作過程中已完成的任務數量,小于或等于taskCount。

largestPoolSize:線程池裡曾經建立過的最大線程數量。通過這個資料可以知道線程池是

否曾經滿過。如該數值等于線程池的最大大小,則表示線程池曾經滿過。

getPoolSize:線程池的線程數量。如果線程池不銷毀的話,線程池裡的線程不會自動銷

毀,是以這個大小隻增不減。

getActiveCount:擷取活動的線程數。

也可以通過擴充線程池進行監控。可以通過繼承線程池來自定義線程池,重寫線程池的beforeExecute、afterExecute和terminated方法,也可以在任務執行前、執行後和線程池關閉前執行一些代碼來進行監控。

例如,監控任務的平均執行時間、最大執行時間和最小執行時間等,這幾個方法線上程池裡是空方法 

參考:《Java并發程式設計的藝術》

參考:http://blog.51cto.com/10594544/2176728