深入理解Java并發(fā)編程之LinkedBlockingQueue隊(duì)列
前面一篇文章我們介紹了使用CAS算法實(shí)現(xiàn)的非阻塞隊(duì)列ConcurrentLinedQueue, 下面我們來(lái)介紹使用獨(dú)占鎖實(shí)現(xiàn)的阻塞隊(duì)列LinkedBlockingQueue。
LinkedBlockingQueue也是使用單向鏈表實(shí)現(xiàn)的,其也有兩個(gè)Node,分別用來(lái)存放首、尾節(jié)點(diǎn),并且還有一個(gè)初始值為0的原子變量count,用來(lái)記錄隊(duì)列元素個(gè)數(shù)。另外還有兩個(gè)ReentrantLock的實(shí)例,分別用來(lái)控制元素入隊(duì)和出隊(duì)的原子性,其中takeLock用來(lái)控制同時(shí)只有一個(gè)線(xiàn)程可以從隊(duì)列頭獲取元素,其他線(xiàn)程必須等待,putLock控制同時(shí)只能有一個(gè)線(xiàn)程可以獲取鎖,在隊(duì)列尾部添加元素,其他線(xiàn)程必須等待。另外,notEmpty 和 notFull 是條件變量,它們內(nèi)部都有一個(gè)條件隊(duì)列用來(lái)存放進(jìn)隊(duì)和出隊(duì)時(shí)被阻塞的線(xiàn)程,其實(shí)這是生產(chǎn)者-消費(fèi)者模型。如下是獨(dú)占鎖的創(chuàng)建代碼。
private final AtomicInteger count = new AtomicInteger(); /** Lock held by take, poll, etc */ private final ReentrantLock takeLock = new ReentrantLock(); /** Wait queue for waiting takes */ private final Condition notEmpty = takeLock.newCondition(); /** Lock held by put, offer, etc */ private final ReentrantLock putLock = new ReentrantLock(); /** Wait queue for waiting puts */ private final Condition notFull = putLock.newCondition();
當(dāng)調(diào)用線(xiàn)程在LinkedBlockingQueue 實(shí)例上執(zhí)行take、poll 等操作時(shí)需要獲取到 takeLock 鎖,從而保證同時(shí)只有一個(gè)線(xiàn)程可以操作鏈表頭節(jié)點(diǎn)。另外由于條件變量 notEmpty 內(nèi)部的條件隊(duì)列的維護(hù)使用的是takeLock的鎖狀態(tài)管理機(jī)制,所以在調(diào)用notEmpty的await 和signal方法前調(diào)用線(xiàn)程必須先獲取到 takeLock鎖,否則會(huì)拋出IllegalMonitorStateException 異常。notEmpty內(nèi)部則維護(hù)著一個(gè)條件隊(duì)列,當(dāng)線(xiàn)程獲取到takeLock 鎖后調(diào)用 notEmpty的await 方法時(shí),調(diào)用線(xiàn)程會(huì)被阻塞,然后該線(xiàn)程會(huì)被放到notEmpty內(nèi)部的條件隊(duì)列進(jìn)行等待,直到有線(xiàn)程調(diào)用了notEmpty的 signal 方法。
在LinkedBlockingQueue實(shí)例上執(zhí)行put、offer等操作時(shí)需要獲取到putLock鎖,從而保證同時(shí)只有一個(gè)線(xiàn)程可以操作鏈表尾節(jié)點(diǎn)。同樣由于條件變量 notFull 內(nèi)部的條件隊(duì)列的維護(hù)使用的是putLock的鎖狀態(tài)管理機(jī)制,所以在調(diào)用 notFull 的 await 和 signal 方法前調(diào)用線(xiàn)程必須先獲取到putLock鎖,否則會(huì)拋出 IllegalMonitorStateException 異常。notFull 內(nèi)部則維護(hù)著一個(gè)條件隊(duì)列,當(dāng)線(xiàn)程獲取到 putLock 鎖后調(diào)用notFull的await 方法時(shí),調(diào)用線(xiàn)程會(huì)被阻塞,然后該線(xiàn)程會(huì)被放到notFull 內(nèi)部的條件隊(duì)列進(jìn)行等待,直到有線(xiàn)程調(diào)用了 notFull 的 signal 方法。如下是LinkedBlockingQueue 的無(wú)參構(gòu)造函數(shù)的代碼。
如下是LinkedBlockingQueue的無(wú)參構(gòu)造代碼
public static final int MAX_VALUE = 0x7fffffff; public LinkedBlockingQueue() { this(Integer.MAX_VALUE); } public LinkedBlockingQueue(int capacity) { if (capacity <= 0) throw new IllegalAgrumentException(); this.capacity = capacity; last = head = new Node<E>(null); }
由該代碼可知,默認(rèn)隊(duì)列容量為0x7fffffff,用戶(hù)也可以自己指定容量,所以從一定程度上可以說(shuō)LinkedBlockingQueue是有界阻塞隊(duì)列。
offer操作
public boolean offer(E e) { //(1) if (e == null) throw new NullPointerException(); //(2) final AtomicInteger count = this.count; if (count.get() == capacity) return false; //(3) int c = -1; Node<E> node = new Node<E>(e); final ReentrantLock putLock = this.putLock; putLock.lock(); try { //(4) if (count.get() < capacity) { enqueue(node); c = count.getAndIncrement(); //(5) if (c + 1 < capacity) notFull.signal(); } } finally { //(6) putLock.unlock(); } //(7) if (c == 0) signalNotEmpty(); //(8) return c >= 0; }
代碼(2)判斷如果當(dāng)前隊(duì)列已滿(mǎn)則丟棄當(dāng)前元素并返回false
代碼(3)獲取到 putLock 鎖,當(dāng)前線(xiàn)程獲取到該鎖后,則其他調(diào)用put和 offer操的線(xiàn)程將會(huì)被阻塞(阻塞的線(xiàn)程被放到putLock鎖的AQS阻塞隊(duì)列)。
代碼(4)這里重新判斷當(dāng)前隊(duì)列是否滿(mǎn),這是因?yàn)樵趫?zhí)行代碼(2)和獲取到 putLock 鎖期間可能其他線(xiàn)程通過(guò) put 或者offer 操作向隊(duì)列里面添加了新元素。重新判斯隊(duì)列確實(shí)不滿(mǎn)則新元素入隊(duì),并遞增計(jì)數(shù)器。
代碼(5)判斷如果新元素入隊(duì)后隊(duì)列還有空閑空間,則喚醒notFull的條件隊(duì)列里面因?yàn)檎{(diào)用了notFull的await操作(比如執(zhí)行put方法而隊(duì)列滿(mǎn)了的時(shí)候)而被阻塞的一個(gè)線(xiàn)程,因?yàn)殛?duì)列現(xiàn)在有空閑所以這里可以提前喚醒一個(gè)入隊(duì)線(xiàn)程。
代碼(6)則釋放獲取的putLock 鎖,這里要注意,鎖的釋放一定要在finally里面做因?yàn)榧词箃ry塊拋出異常了,finally也是會(huì)被執(zhí)行到。另外釋放鎖后其他因?yàn)檎{(diào)用put 操作而被阻塞的線(xiàn)程將會(huì)有一個(gè)獲取到該鎖。
代碼(7)中的c0說(shuō)明在執(zhí)行代碼(6)釋放鎖時(shí)隊(duì)列里面至少有一個(gè)元素,隊(duì)列里面有元素則執(zhí)行signalNotEmpty操作.
到此這篇關(guān)于深入理解Java并發(fā)編程之LinkedBlockingQueue隊(duì)列的文章就介紹到這了,更多相關(guān)Java LinkedBlockingQueue隊(duì)列內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java計(jì)算兩個(gè)時(shí)間相差天數(shù)的方法匯總
這篇文章主要介紹了java計(jì)算兩個(gè)時(shí)間相差天數(shù)的方法,感興趣的小伙伴們可以參考一下2015-11-11Spring Boot詳解各類(lèi)請(qǐng)求和響應(yīng)的處理方法
平時(shí)只是在用SpringBoot框架,但并沒(méi)有詳細(xì)研究過(guò)請(qǐng)求和響應(yīng)執(zhí)行的一個(gè)具體過(guò)程,所以本文主要來(lái)梳理一下SpringBoot請(qǐng)求和響應(yīng)的處理過(guò)程2022-07-07Spring中事務(wù)管理方案和事務(wù)管理器及事務(wù)控制的API詳解
這篇文章主要介紹了Spring中事務(wù)管理方案和事務(wù)管理器及事務(wù)控制的API詳解,事務(wù)管理是指對(duì)事務(wù)進(jìn)行管理和控制,以確保事務(wù)的正確性和完整性,事務(wù)管理的作用是保證數(shù)據(jù)庫(kù)的數(shù)據(jù)操作的一致性和可靠性,需要的朋友可以參考下2023-08-08SpringBoot整合Spring Data Elasticsearch的過(guò)程詳解
這篇文章主要介紹了SpringBoot整合Spring Data Elasticsearch的過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09Springboot整合PageOffice 實(shí)現(xiàn)word在線(xiàn)編輯保存功能
這篇文章主要介紹了Springboot整合PageOffice 實(shí)現(xiàn)word在線(xiàn)編輯保存,本文以Samples5 為示例文件結(jié)合示例代碼給大家詳細(xì)介紹,需要的朋友可以參考下2021-08-08關(guān)于SpringCloud的Bus消息總線(xiàn)圖文詳解
這篇文章主要介紹了關(guān)于SpringCloud的Bus消息總線(xiàn)圖文詳解,Spring Cloud Bus是用來(lái)將分布式系統(tǒng)的節(jié)點(diǎn)與輕量級(jí)消息系統(tǒng)鏈接起來(lái)的框架,它整合了Java的事件處理機(jī)制和消息中間件的功能,需要的朋友可以參考下2023-05-05Java基于正則表達(dá)式獲取指定HTML標(biāo)簽指定屬性值的方法
這篇文章主要介紹了Java基于正則表達(dá)式獲取指定HTML標(biāo)簽指定屬性值的方法,涉及java基于正則的HTML元素匹配相關(guān)操作技巧,需要的朋友可以參考下2017-01-01SpringBoot MockMvc單元測(cè)試的示例代碼
這篇文章主要介紹了SpringBoot MockMvc單元測(cè)試的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11