前言
在一次項(xiàng)目中冠桃,偶遇BlockingQueue命贴,特意查了下用法,使我對(duì)它有了強(qiáng)列的興趣食听,經(jīng)過一段時(shí)間的學(xué)習(xí)胸蛛,將其整理,用圖解的方式解釋樱报,方便理解葬项。
介紹
在新增的Concurrent包中,BlockingQueue很好的解決了多線程中迹蛤,如何高效安全“傳輸”數(shù)據(jù)的問題民珍。通過這些高效并且線程安全的隊(duì)列類,為我們快速搭建高質(zhì)量的多線程程序帶來極大的便利盗飒。圖(1_0.png)是其繼承關(guān)系嚷量,可以看出BlockingQueue是繼承Queue。
我們先看下BlockQueue的圖解逆趣,通過圖蝶溶,我們很容易理解,這是一個(gè)隊(duì)列基本圖宣渗,在圖中抖所,我們發(fā)現(xiàn)入隊(duì)有put、add落包、offer三個(gè)方法。出隊(duì)有poll摊唇、remove咐蝇、take三個(gè)方法,就是因?yàn)檫@幾個(gè)方法巷查,使得blockQueue功能很強(qiáng)大有序。
put、add岛请、offer 區(qū)別
- put
把Object加到BlockingQueue里,如果BlockQueue沒有空間,則調(diào)用此方法的線程被阻斷直到BlockingQueue里面有空間再繼續(xù). - add
把Object加到BlockingQueue里,即如果BlockingQueue可以容納,則返回true,否則報(bào)異常 - offer
將Object加到BlockingQueue里,即如果BlockingQueue可以容納,則返回true,否則返回false.
當(dāng)隊(duì)列滿了旭寿,put阻塞,等有了再加崇败,add直接報(bào)錯(cuò)盅称,offer返回狀態(tài)
poll肩祥、remove、take 區(qū)別
- take
取走BlockingQueue里排在首位的對(duì)象,若BlockingQueue為空,阻斷進(jìn)入等待狀態(tài)直到Blocking有新的對(duì)象被加入為止 - remove
取走BlockingQueue里排在首位的對(duì)象,若不能立即取出,則拋出異常 - poll
取走BlockingQueue里排在首位的對(duì)象,若不能立即取出,則可以等time參數(shù)規(guī)定的時(shí)間,
取不到時(shí)返回null;
當(dāng)隊(duì)列空了缩膝,take取不到就等混狠,remove就拋異常,poll就返回null
阻塞模型
-
當(dāng)隊(duì)列滿了疾层,再往隊(duì)列里put數(shù)據(jù)就會(huì)出現(xiàn)線程等待将饺,模型如下
Paste_Image.png 當(dāng)隊(duì)列空了,再往隊(duì)列里take數(shù)據(jù)痛黎,就會(huì)出現(xiàn)線程等待予弧,模型如下
實(shí)現(xiàn)了BlockingQueue的類
我們常用的隊(duì)列就linkedBlockingQueue和ArrayBlockingQueue。
LinkedBlockingQueue 和 ArrayBlockingQueue
- 區(qū)別
我們區(qū)分下LinkedBlockingQueue和ArrayBlockingQueue湖饱,凡是linked打頭的都是內(nèi)部維護(hù)一個(gè)鏈表掖蛤,Array打頭的是維護(hù)一個(gè)數(shù)組,實(shí)現(xiàn)方式不一樣琉历,功能也有所區(qū)別坠七。 - LinkedBlockingQueue是內(nèi)部維護(hù)一個(gè)鏈表,當(dāng)執(zhí)行put方法時(shí)旗笔,程序會(huì)先獲得一個(gè)重入鎖彪置,防止多線程同時(shí)操作產(chǎn)生數(shù)據(jù)不正確,然后后生成一個(gè)數(shù)據(jù)節(jié)點(diǎn)(Node)添加到鏈表的最后面蝇恶。如果隊(duì)列滿拳魁,則使用java.util.concurrent.locks.Condition的await()方法來阻塞繼續(xù)添加。
這是put方法源碼:
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await();
}
enqueue(e);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
take()取數(shù)據(jù)也用了await()方法撮弧,了解原理后潘懊,發(fā)現(xiàn)取數(shù)據(jù)時(shí)當(dāng)鏈表為空時(shí),就阻塞贿衍。
這是take()方法源碼:
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
} - ArrayBlockingQueue是內(nèi)部維護(hù)一個(gè)數(shù)組授舟,當(dāng)執(zhí)行put方法時(shí),程序也會(huì)先獲得一個(gè)重入鎖贸辈,防止多純種同時(shí)操作數(shù)組释树,然后直接插入到數(shù)組上面,這里和linkedBlockingQueue有個(gè)區(qū)別就是不會(huì)產(chǎn)生一個(gè)新的對(duì)象節(jié)點(diǎn)擎淤,開銷上插入比LinkedBlockQueue節(jié)省空間奢啥。
這是put方法源碼:
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final E[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == items.length)
notFull.await();
} catch (InterruptedException ie) {
notFull.signal(); // propagate to non-interrupted thread
throw ie;
}
insert(e);
} finally {
lock.unlock();
}
} -
總結(jié)
這兩個(gè)隊(duì)列的實(shí)現(xiàn)幾乎都是相似的,我們可以理解成對(duì)應(yīng)的數(shù)組和鏈表的實(shí)現(xiàn)嘴拢。并且是線程安全的桩盲,在特定場(chǎng)景中使用特定的實(shí)現(xiàn)類能保證高效和空間的合理性。