線程池中的隊列要求的是阻塞隊列,作用主要是當線程池處理任務能力不足時,隊列存儲多餘的任務,進而起到削峰和緩沖的目的。
可以選擇的隊列種類很多,如何選擇合适的隊列應用到自己的線程池中?就需要了解他們的優缺點,進而擇優使用
常見的阻塞隊列都是以基于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------
如果這個辦法對您有用,或者您希望持續關注,也可以掃描下方二維碼或者在微信公衆号中搜尋