還不了解Java的五大BlockingQueue阻塞隊(duì)列源碼,看這篇文章就夠了
引言
最近一個(gè)月一直在更新《解讀Java源碼專欄》,其中跟大家一起剖析了Java的常見的5種BlockingQueue(阻塞隊(duì)列),今天就盤點(diǎn)一下這幾種阻塞隊(duì)列的優(yōu)缺點(diǎn)、區(qū)別,以及應(yīng)用場景。
常見的BlockingQueue有以下5種,下面會(huì)詳細(xì)介紹。
- ArrayBlockingQueue
基于數(shù)組實(shí)現(xiàn)的阻塞隊(duì)列,創(chuàng)建隊(duì)列時(shí)需指定容量大小,是有界隊(duì)列。
- LinkedBlockingQueue
基于鏈表實(shí)現(xiàn)的阻塞隊(duì)列,默認(rèn)是無界隊(duì)列,創(chuàng)建可以指定容量大小
- SynchronousQueue
一種沒有緩沖的阻塞隊(duì)列,生產(chǎn)出的數(shù)據(jù)需要立刻被消費(fèi)
- PriorityBlockingQueue
實(shí)現(xiàn)了優(yōu)先級(jí)的阻塞隊(duì)列,可以按照元素大小排序,是無界隊(duì)列
- DelayQueue
實(shí)現(xiàn)了延遲功能的阻塞隊(duì)列,基于PriorityQueue實(shí)現(xiàn)的,是無界隊(duì)列
BlockingQueue簡介
這幾種阻塞隊(duì)列都是實(shí)現(xiàn)了BlockingQueue接口,在日常開發(fā)中,我們好像很少用到BlockingQueue(阻塞隊(duì)列),BlockingQueue到底有什么作用?應(yīng)用場景是什么樣的?
如果使用過線程池或者閱讀過線程池源碼,就會(huì)知道線程池的核心功能都是基于BlockingQueue實(shí)現(xiàn)的。
大家用過消息隊(duì)列(MessageQueue),就知道消息隊(duì)列作用是解耦、異步、削峰。同樣BlockingQueue的作用也是這三種,區(qū)別是BlockingQueue只作用于本機(jī)器,而消息隊(duì)列相當(dāng)于分布式BlockingQueue。
BlockingQueue作為阻塞隊(duì)列,主要應(yīng)用于生產(chǎn)者-消費(fèi)者模式的場景,在并發(fā)多線程中尤其常用。
- 比如像線程池中的任務(wù)調(diào)度場景,提交任務(wù)和拉取并執(zhí)行任務(wù)。
- 生產(chǎn)者與消費(fèi)者解耦的場景,生產(chǎn)者把數(shù)據(jù)放到隊(duì)列中,消費(fèi)者從隊(duì)列中取數(shù)據(jù)進(jìn)行消費(fèi)。兩者進(jìn)行解耦,不用感知對(duì)方的存在。
- 應(yīng)對(duì)突發(fā)流量的場景,業(yè)務(wù)高峰期突然來了很多請(qǐng)求,可以放到隊(duì)列中緩存起來,消費(fèi)者以正常的頻率從隊(duì)列中拉取并消費(fèi)數(shù)據(jù),起到削峰的作用。
BlockingQueue是個(gè)接口,定義了幾組放數(shù)據(jù)和取數(shù)據(jù)的方法,來滿足不同的場景。
操作 | 拋出異常 | 返回特定值 | 阻塞 | 阻塞一段時(shí)間 |
放數(shù)據(jù) | add() | offer() | put() | offer(e, time, unit) |
取數(shù)據(jù)(同時(shí)刪除數(shù)據(jù)) | remove() | poll() | take() | poll(time, unit) |
取數(shù)據(jù)(不刪除) | element() | peek() | 不支持 | 不支持 |
這四組方法的區(qū)別是:
- 當(dāng)隊(duì)列滿的時(shí)候,再次添加數(shù)據(jù),add()會(huì)拋出異常,offer()會(huì)返回false,put()會(huì)一直阻塞,offer(e, time, unit)會(huì)阻塞指定時(shí)間,然后返回false。
- 當(dāng)隊(duì)列為空的時(shí)候,再次取數(shù)據(jù),remove()會(huì)拋出異常,poll()會(huì)返回null,take()會(huì)一直阻塞,poll(time, unit)會(huì)阻塞指定時(shí)間,然后返回null。
ArrayBlockingQueue
- ArrayBlockingQueue底層基于數(shù)組實(shí)現(xiàn),采用循環(huán)數(shù)組,提升了數(shù)組的空間利用率。
- ArrayBlockingQueue初始化的時(shí)候,必須指定隊(duì)列長度,是有界的阻塞隊(duì)列,所以要預(yù)估好隊(duì)列長度,保證生產(chǎn)者和消費(fèi)者速率相匹配。
- ArrayBlockingQueue的方法是線程安全的,使用ReentrantLock在操作前后加鎖來保證線程安全。
LinkedBlockingQueue
- LinkedBlockingQueue底層基于鏈表實(shí)現(xiàn),支持從頭部彈出數(shù)據(jù),從尾部添加數(shù)據(jù)。
- LinkedBlockingQueue初始化的時(shí)候,如果不指定隊(duì)列長度,默認(rèn)長度是Integer最大值,相當(dāng)于無界隊(duì)列,有內(nèi)存溢出風(fēng)險(xiǎn),建議初始化的時(shí)候指定隊(duì)列長度。
- LinkedBlockingQueue的方法是線程安全的,分別使用了讀寫兩把鎖,比ArrayBlockingQueue性能更好。
與ArrayBlockingQueue區(qū)別是:
- 底層結(jié)構(gòu)不同,ArrayBlockingQueue底層基于數(shù)組實(shí)現(xiàn),初始化的時(shí)候必須指定數(shù)組長度,無法擴(kuò)容。LinkedBlockingQueue底層基于鏈表實(shí)現(xiàn),鏈表最大長度是Integer最大值。
- 占用內(nèi)存大小不同,ArrayBlockingQueue一旦初始化,數(shù)組長度就確定了,不會(huì)隨著元素增加而改變。LinkedBlockingQueue會(huì)隨著元素越多,鏈表越長,占用內(nèi)存越大。
- 性能不同,ArrayBlockingQueue的入隊(duì)和出隊(duì)共用一把鎖,并發(fā)較低。LinkedBlockingQueue入隊(duì)和出隊(duì)使用兩把獨(dú)立的鎖,并發(fā)情況下性能更高。
- 公平鎖選項(xiàng),ArrayBlockingQueue初始化的時(shí)候,可以指定使用公平鎖或者非公平鎖,公平鎖模式下,可以按照線程等待的順序來操作隊(duì)列。LinkedBlockingQueue只支持非公平鎖。
- 適用場景不同,ArrayBlockingQueue適用于明確限制隊(duì)列大小的場景,防止生產(chǎn)速度大于消費(fèi)速度的時(shí)候,造成內(nèi)存溢出、資源耗盡。LinkedBlockingQueue適用于業(yè)務(wù)高峰期可以自動(dòng)擴(kuò)展消費(fèi)速度的場景。
SynchronousQueue
無論是ArrayBlockingQueue還是LinkedBlockingQueue都是起到緩沖隊(duì)列的作用,當(dāng)消費(fèi)者的消費(fèi)速度跟不上時(shí),任務(wù)就在隊(duì)列中堆積,需要等待消費(fèi)者慢慢消費(fèi)。
如果我們想要自己的任務(wù)快速執(zhí)行,不要積壓在隊(duì)列中,該怎么辦?這時(shí)候就可以使用SynchronousQueue了。
SynchronousQueue被稱為同步隊(duì)列,當(dāng)生產(chǎn)者往隊(duì)列中放元素的時(shí)候,必須等待消費(fèi)者把這個(gè)元素取走,否則一直阻塞。消費(fèi)者取元素的時(shí)候,同理也必須等待生產(chǎn)者放隊(duì)列中放元素。
- SynchronousQueue底層有兩種實(shí)現(xiàn)方式,分別是基于棧實(shí)現(xiàn)非公平策略,以及基于隊(duì)列實(shí)現(xiàn)的公平策略。
- SynchronousQueue初始化的時(shí)候,可以指定使用公平策略還是非公平策略。
- SynchronousQueue不存儲(chǔ)元素,不適合作為緩存隊(duì)列使用。適用于生產(chǎn)者與消費(fèi)者速度相匹配的場景,可減少任務(wù)執(zhí)行的等待時(shí)間。
PriorityBlockingQueue
由于PriorityQueue跟前幾個(gè)阻塞隊(duì)列不一樣,并沒有實(shí)現(xiàn)BlockingQueue接口,只是實(shí)現(xiàn)了Queue接口,所以PriorityQueue并不算阻塞隊(duì)列。Queue接口中定義了幾組放數(shù)據(jù)和取數(shù)據(jù)的方法,來滿足不同的場景。
- PriorityQueue實(shí)現(xiàn)了Queue接口,提供了兩組放數(shù)據(jù)和讀數(shù)據(jù)的方法,來滿足不同的場景。
- PriorityQueue底層基于數(shù)組實(shí)現(xiàn),實(shí)現(xiàn)了按照元素值大小排序的功能,內(nèi)部按照最小堆存儲(chǔ),實(shí)現(xiàn)了高效的插入和刪除。
- PriorityQueue初始化的時(shí)候,可以指定數(shù)組長度和自定義比較器。
- PriorityQueue初始容量是11,當(dāng)數(shù)組容量小于64,采用2倍擴(kuò)容,否則采用1.5擴(kuò)容。
- PriorityQueue每次都是從數(shù)組頭節(jié)點(diǎn)取元素,取之后需要調(diào)整最小堆。
DelayQueue
DelayQueue是一種本地延遲隊(duì)列,比如希望我們的任務(wù)在5秒后執(zhí)行,就可以使用DelayQueue實(shí)現(xiàn)。常見的使用場景有:
- 訂單10分鐘內(nèi)未支付,就取消。
- 緩存過期后,就刪除。
- 消息的延遲發(fā)送等。
- DelayQueue底層采用組合的方式,復(fù)用PriorityQueue的按照延遲時(shí)間排序任務(wù)的功能,實(shí)現(xiàn)了延遲隊(duì)列。
- DelayQueue是線程安全的,內(nèi)部使用ReentrantLock加鎖。
總結(jié)
這5種阻塞隊(duì)列的特性各不相同,在使用的時(shí)候該怎么選擇呢?我做了一張圖,供大家參考。