天天看點

阻塞隊列模型和線程池

阻塞隊列模型介紹

阻塞隊列模型和線程池息息相關,是以本篇部落格先介紹阻塞隊列的相關知識。如下圖所示:

<a href="http://s4.51cto.com/wyfs02/M01/7E/12/wKiom1b2RQ2SNHWrAAAu0KDMMdQ915.png" target="_blank"></a>

首先我們來說,什麼是Queue,然後在談什麼是BlockingQueue。

那麼什麼是Queue呢?一句話,就是一端進,另一端出,這樣就形成了First In , First Out,即先進先出。而BlockingQueue隻不過是在Queue的基礎上進行了2個附加操作而已:如果Queue空,那麼Out線程阻塞,如果Queue滿,那麼In線程阻塞。

了解了上面的Queue/BlockingQueue,那麼就好了解Deque/BlockingDeque了。

Deque,就是雙端隊列,其實就是說在2端,都可以進行IN/OUT,當然如果我們隻在同一端進行IN/OUT,那麼自然形成了棧結構(先進後出)。

其次,我們先來看看java.util.concurrent.BlockingQueue的API清單:

<a href="http://s3.51cto.com/wyfs02/M01/7E/12/wKiom1b2RsSBMEufAAAd4AxQv2M881.png" target="_blank"></a>

其實從這裡可以看出JAVA API的一個思路,往往一個操作提供多種選擇:

如果隊列是空的,那麼消費線程該如何處理呢?

可以立刻抛出異常,也可以傳回false/NULL,也可以過一段時間在TRY...

好了,到這裡,我們隻看到了接口API,如果要你來實作,你會怎麼做呢?又會有哪些疑問呢?

思考:

是否應該提供一個存儲,來放置隊列中的元素呢?

這個存儲應該多大呢?可以是什麼形式呢?

隊列中的元素是否存在優先級排序呢?

對隊列中的元素進行操作時,是否應該有鎖的控制呢?

一端IN,另一端OUT,這2個操作可以同時進行嗎?

帶着這些疑問,我們來對典型的BlockingQueue來進行分析。

ArrayBlockingQueue PK LinkedBlockingQueue

存儲PK:

從名稱上就可以知道,一個用的是數組,另一個用的是連結清單。看看源碼驗證下:

ArrayBlockingQueue:

<a href="http://s3.51cto.com/wyfs02/M02/7E/12/wKiom1b2TBqykUFqAAAHWCwuOfk038.png" target="_blank"></a>

LinkedBlockingQueue:

<a href="http://s5.51cto.com/wyfs02/M02/7E/12/wKiom1b2TDGTfNZ7AAArcPBhXaU329.png" target="_blank"></a>

容量PK:

ArrayBlockingQueue可以通過構造方法指定容量:

<a href="http://s4.51cto.com/wyfs02/M00/7E/0E/wKioL1b2TWqAkAZEAAAilXAs8XU931.png" target="_blank"></a>

而LinkedBlockingQueue如果在初始化時不指定容量,那麼将是Integer.MAX_VALUE,這一點很重要,特别是在生産者的速度大于消費者的速度,由于此時無容量限制,将導緻隊列中的元素開始膨脹,那麼将消耗掉大量系統資源。

<a href="http://s5.51cto.com/wyfs02/M02/7E/0E/wKioL1b2TiPjk5wWAABUl54WecY009.png" target="_blank"></a>

鎖PK:

在ArrayBlockingQueue中隻有一個鎖:

<a href="http://s3.51cto.com/wyfs02/M00/7E/12/wKiom1b2TdKAEXG_AAAJ8rP7w4w494.png" target="_blank"></a>

而在LinkedBlockingQueue中有2個鎖:

<a href="http://s3.51cto.com/wyfs02/M01/7E/0E/wKioL1b2TqzhaX9HAAANfu9FdwE655.png" target="_blank"></a>

<a href="http://s3.51cto.com/wyfs02/M00/7E/12/wKiom1b2TjeAQpuMAAAL2dVdzdM519.png" target="_blank"></a>

其實到這裡,我們已經可以大緻猜測出,LinkedBlockingQueue對于take/put使用了分别的鎖,進而比ArrayBlockingQueue在高并發下更具優勢。

我們再來看看其他BlockingQueue:

DelayQueue:延遲隊列,實際上是說,隊列中的元素生效的話,有個時間差。

PriorityQueue:優先級隊列,會提供Comparator來進行隊列中的元素的排序。

SynchronousQueue:這個隊列比較特殊,因為沒有存儲機制,實際上隻是做了一個生産者和消費者的傳遞機制。

線程池介紹

如果任務到達時,才開始建立線程,這實際上會讓任務的執行被延遲,于是産生了線程池的概念,如果在池子中已經存在了一批線程,那麼任務到達時自然省去了線程建立的時間,相當于提高了響應速度。其次,如果線程執行完任務後,在放入池子中,這相當于在複用線程,達到了資源節約的目的。當然,如果任務的執行時間是遠遠大于線程的建立/銷毀時間,其實就無所謂了。

快速建立線程池:Executors

<a href="http://s3.51cto.com/wyfs02/M00/7E/12/wKiom1b2Vsyg8igYAAAxDv6DG20760.png" target="_blank"></a>

Executors提供了一系列的快速建立線程池的方法,比如:

<a href="http://s3.51cto.com/wyfs02/M00/7E/0F/wKioL1b2V-TBFjcAAAAdWmjC9AM355.png" target="_blank"></a>

<a href="http://s3.51cto.com/wyfs02/M01/7E/12/wKiom1b2V2XwcB4ZAAAiRV0Onrs008.png" target="_blank"></a>

<a href="http://s4.51cto.com/wyfs02/M02/7E/0F/wKioL1b2WCDwT9pPAAAc9lhRhko375.png" target="_blank"></a>

建立數量固定的/單個的/緩存的  線程池。

可以看到線程池的建立利用到了上面提及的BlockingQueue,隊列中的元素就是任務Runnable。

方法傳回的都是ExecutorService的實作類:ThreadPoolExecutor。

線程池的核心:ThreadPoolExecutor

我們直接來看看ThreadPoolExecutor的構造方法:

<a href="http://s5.51cto.com/wyfs02/M00/7E/12/wKiom1b2WLOCpoqmAABhcZlS7Bk959.png" target="_blank"></a>

了解這些參數,對于了解線程池的原理有很大幫助:

corePoolSize:線程池的核心線程數量,是線程數目的一個穩定峰值。

maximumPoolSize:線程池的最大線程數量,如果corePoolSize依舊滿足不了需要,那麼可以讓線程增長至maximumPoolSize,一旦需要下降,那麼超出核心線程的那一部分線程資源将被回收。

workQueue:這個隊列是待處理的任務隊列。實際上,在ThreadPoolExecutor中除此之外還存在一個正在處理的工作隊列workers:

<a href="http://s3.51cto.com/wyfs02/M02/7E/0F/wKioL1b2e2yDfnD9AAAI_i_YhEc158.png" target="_blank"></a>

keepAliveTime:超過核心線程數,又小于最大線程數目的線程在空閑的情況下,多久回收。

threadFactory:線程工廠,實際上用的是預設的DefaultThreadFactory,通過代碼發現僅僅是針對Thread做了些設定(比如線程組/線程名稱/背景/優先級等設定),将Runnable挂到Thread上而已。

handler:如果已經達到了最大線程數目,那麼對于任務隻能開始拒絕了,這個就是拒絕處理的政策類。

本文轉自zfz_linux_boy 51CTO部落格,原文連結:http://blog.51cto.com/zhangfengzhe/1755533,如需轉載請自行聯系原作者