天天看點

線程池系列三:動态修改線程池隊列大小

線程池中的隊列要求的是阻塞隊列,作用主要是當線程池處理任務能力不足時,隊列存儲多餘的任務,進而起到削峰和緩沖的目的。

可以選擇的隊列種類很多,如何選擇合适的隊列應用到自己的線程池中?就需要了解他們的優缺點,進而擇優使用

常見的阻塞隊列都是以基于BlockingQueue的實作

ArrayBlockingQueue

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

LinkedBlockingQueue

一個基于連結清單結構的有界阻塞隊列(不設定大小時,預設為Integer.MAX_VALUE),此隊列按FIFO (先進先出) 排序元素。Executors的幾個靜态線程池工廠方法大部分都是使用這個隊列

SynchronousQueue

一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處于阻塞狀态;同理,當每個讀操作的時候,同樣需要一個相比對的寫操作。這裡的 Synchronous 指的就是讀寫操作需要同步,一個讀操作對應一個寫操作。

注:吞吐量通常要高于LinkedBlockingQueue。靜态工廠方法Executors.newCachedThreadPool使用了這個隊列。

DelayQueue

是一個支援延時擷取元素的無界阻塞隊列。内部是基于PriorityQueue的實作。

PriorityBlockingQueue

一個具有優先級的無限阻塞隊列。隻能指定初始的隊列大小,後面插入元素的時候,如果空間不夠的話會自動擴容

注:它是無界隊列,put操作不會阻塞,但take方法在隊列為空的時候會阻塞隊列元素不可以插入null值,同時插入隊列的元素必須是可比較大小的(comparable),否則報 ClassCastException 異常

2.1、ArrayBlockingQueue

是一個基于數組結構的有界阻塞隊列,底層結構是一個數組

線程池系列三:動态修改線程池隊列大小

外部存儲資料時,從頭開始向後周遊數組插入資料,并記錄偏移量,供下次從偏移量位置再次存儲;

而讀取資料時也是一樣,從頭開始向後周遊數組讀取并删除資料,記錄偏移量供下次從偏移量位置再次讀取

另外考慮一件問題:由于數組有界,總會讀寫到最後一個元素。數組前部分讀取後置空,如果不再使用就浪費了。而後部分到達了尾部,隊列不能再插入資料了

這就引入ArrayBlockingQueue的一個設計,即到達尾部後,若頭部空置,則複用頭部資源。

讓線性的有界數組,在邏輯上成為環形數組,進而達到資源複用的目的。

線程池系列三:動态修改線程池隊列大小

2.2、LinkedBlockingQueue

一個基于連結清單結構的有界阻塞隊列(不設定大小時,預設為Integer.MAX_VALUE),底層結構是一個單向連結清單

線程池系列三:動态修改線程池隊列大小

外部存儲資料時在尾部插入資料;而讀取資料時則從頭部讀取并删除節點資料

線程池系列三:動态修改線程池隊列大小

底層結構

數組

(邏輯上環形數組)

單向連結清單

是否有界

有界阻塞隊列

(大小必須聲明)

(不聲明則預設為Integer.MAX_VALUE)

公平鎖

可配置是否使用公平鎖

(預設非公平)

僅支援非公平鎖

讀寫共用一把ReentrantLock鎖

兩個condition判斷滿空狀态

讀寫分别使用一把ReentrantLock鎖

使用各自的condition判斷滿空狀态

count

共用一把鎖,是以隊列内無并發,類型為int

讀寫兩把鎖,隊列記憶體在并發,是以類型為AtomicInteger

其他

由于是連結清單,是以插入的資料要多建立一個Node對象存,會對GC有影響

其他異同點:

LinkedBlockingQueue底層由于是連結清單,是以插入資料時要多建立一個Node對象,是以會對GC有影響

ArrayBlockingQueue從頭開始周遊進行讀寫;而LinkedBlockingQueue則為鍊尾加元素,鍊尾取元素

LinkedBlockingQueue讀寫各加一把鎖,通常比ArrayBlockingQueue具有更高的吞吐量,但是在大多數并發應用程式中,可預測的性能較差。

無論是ArrayBlockingQueue和LinkedBlockingQueue,他們的隊列大小都是不可變的。

ArrayBlockingQueue底層是數組,大小固定。

而LinkedBlockingQueue的capacity則被final修飾,不可修改。

而我們實際項目中,往往需要根據業務實際需要調整隊列大小。那麼如果實作隊列的大小可變?

如果我們想基于ArrayBlockingQueue進行改造,但修改大小必然要涉及到重新建立數組,以及新舊數組的資料遷移問題,有些複雜。

如果考慮基于LinkedBlockingQueue進行改造,我們隻要将修飾capacity的final去掉即可實作動态調整。但有一個問題,LinkedBlockingQueue具體實作中很多是基于capacity不變進行的設計,是以我們需要将涉及的功能進行調整

調整思路: 1、capacity可修改大小:去掉修飾詞final,增加set方法便可動态調整 2、梳理受影響的範圍:将代碼中應用capacity的功能進行梳理,通過capacity的改動使其相容正常功能

具體進行以下調整,其他内容不變:

4.1、類名修改

将LinkedBlockingQueue的代碼實作拷貝并修改類名為ResizeLinkedBlockingQueue

4.2、将capacity的修飾詞final去掉,增加volatile修飾詞。改動後使其立即生效。并設定capacity的set方法,并在set時限制大小

線程池系列三:動态修改線程池隊列大小

4.3、梳理capacity所涉及的各個應用點,進行調整

主要是存儲資料的幾個方法條件判斷時進行改動

線程池系列三:動态修改線程池隊列大小
線程池系列三:動态修改線程池隊列大小
線程池系列三:動态修改線程池隊列大小
線程池系列三:動态修改線程池隊列大小
線程池系列三:動态修改線程池隊列大小

------The End------

如果這個辦法對您有用,或者您希望持續關注,也可以掃描下方二維碼或者在微信公衆号中搜尋

線程池系列三:動态修改線程池隊列大小