LinkedBlockingQueue 是單向鏈表結(jié)構(gòu)的自定義容量的阻塞隊(duì)列歇僧,元素操作按照FIFO(first-in-first-out 先入先出)的順序根盒,使用顯式鎖 ReentrantLock 和 Condition 來保證線程安全乾颁。鏈表結(jié)構(gòu)的隊(duì)列通常比基于數(shù)組的隊(duì)列(ArrayBlockingQueue)有更高的吞吐量缓呛,但是在并發(fā)環(huán)境下性能卻不如數(shù)組隊(duì)列。因?yàn)楸容^簡單靠欢,本章本來是不在筆者的寫作范圍內(nèi)的廊敌,但是在后面的線程池源碼中用到了LinkedBlockingQueue,我們我們就來簡單看一下门怪,加深一下印象骡澈。
本章應(yīng)該是隊(duì)列篇的終章了掷空,還有LinkedBlockingDeque、ArrayBlockingQueue這些比較簡單的隊(duì)列就不再講解了官地,后面我們會開始線程池相關(guān)源碼分析区丑。
概述
LinkedBlockingQueue(后稱LBQ)隊(duì)列容量可通過參數(shù)來自定義沧侥,并且內(nèi)部是不會自動擴(kuò)容的魄鸦。如果未指定容量拾因,將取最大容量
Integer.MAX_VALUE
绢记。 如果你理解了前幾篇我們所講的隊(duì)列,那么你會發(fā)現(xiàn) LBQ 非常容易理解跪解,內(nèi)部沒有太多復(fù)雜的算法签孔,數(shù)據(jù)結(jié)構(gòu)也是使用了簡單的鏈表結(jié)構(gòu)饥追。
數(shù)據(jù)結(jié)構(gòu)
標(biāo)準(zhǔn)的隊(duì)列繼承關(guān)系救崔,不多贅述帚豪。
重要屬性
//容量
private final int capacity;
//元素個數(shù)
private final AtomicInteger count = new AtomicInteger();
//鏈表頭
transient Node<E> head;
//鏈表尾
private transient Node<E> last;
//出列鎖
private final ReentrantLock takeLock = new ReentrantLock();
//等待獲取(出隊(duì))條件
private final Condition notEmpty = takeLock.newCondition();
//入列鎖
private final ReentrantLock putLock = new ReentrantLock();
//等待插入(入列)條件
private final Condition notFull = putLock.newCondition();
LBQ 在實(shí)現(xiàn)多線程對競爭資源的互斥訪問時(shí)狸臣,對于入列和出列操作分別使用了不同的鎖烛亦。對于入列操作,通過putLock
進(jìn)行同步铐达;對于出列操作瓮孙,通過takeLock
進(jìn)行同步杭抠。
此外偏灿,插入鎖putLock
和出列條件notFull
相關(guān)聯(lián)翁垂,出列鎖takeLock
和出列條件notEmpty
相關(guān)聯(lián)沿猜。通過notFull
和notEmpty
更細(xì)膩的控制鎖邢疙。
- 若某線程(線程A)要取出數(shù)據(jù)時(shí)望薄,隊(duì)列正好為空痕支,則該線程會執(zhí)行
notEmpty.await()
進(jìn)行等待卧须;當(dāng)其它某個線程(線程B)向隊(duì)列中插入了數(shù)據(jù)之后花嘶,會調(diào)用notEmpty.signal()
喚醒notEmpty
上的等待線程蹦漠。此時(shí)笛园,線程A會被喚醒從而得以繼續(xù)運(yùn)行。 此外州叠,線程A在執(zhí)行取操作前咧栗,會獲取takeLock
致板,在取操作執(zhí)行完畢再釋放takeLock
可岂。 - 若某線程(線程H)要插入數(shù)據(jù)時(shí)缕粹,隊(duì)列已滿平斩,則該線程會它執(zhí)行
notFull.await()
進(jìn)行等待绘面;當(dāng)其它某個線程(線程I)取出數(shù)據(jù)之后揭璃,會調(diào)用notFull.signal()
喚醒notFull
上的等待線程瘦馍。此時(shí)情组,線程H就會被喚醒從而得以繼續(xù)運(yùn)行院崇。 此外底瓣,線程H在執(zhí)行插入操作前濒持,會獲取putLock
柑营,在插入操作執(zhí)行完畢才釋放putLock
官套。
源碼解析
put(E e)
LBQ 的添加元素的方法有offer()奶赔、put()
另伍,put
是在隊(duì)列已滿的情況下等待绞旅,而offer
則直接返回結(jié)果堕汞,它們內(nèi)部操作都一致讯检。所這里我們只對put
進(jìn)行解析
//尾部插入節(jié)點(diǎn),隊(duì)列滿時(shí)會一直等待可用,響應(yīng)中斷
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;//獲取入列鎖
final AtomicInteger count = this.count;//獲取元素?cái)?shù)
putLock.lockInterruptibly();//響應(yīng)中斷式加鎖
try {
while (count.get() == capacity) {
notFull.await();//隊(duì)列已滿,等待
}
enqueue(node);//節(jié)點(diǎn)添加到隊(duì)列尾
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
說明:看源碼吧挡毅。
poll()
LBQ 的獲取元素的方法有poll()段磨、take()苹支、peek()
究反,take
在隊(duì)列為空的情況下會一直等待琅锻,poll
不等待直接返回結(jié)果恼蓬,peek
是獲取但不移除頭結(jié)點(diǎn)元素小槐,內(nèi)部操作都差不多凿跳。這里我們只對take
進(jìn)行解析:
/**獲取并消除頭節(jié)點(diǎn),會一直等待隊(duì)列可用,響應(yīng)中斷*/
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;//獲取出列鎖
takeLock.lockInterruptibly();//響應(yīng)中斷式加鎖
try {
while (count.get() == 0) {
notEmpty.await();//隊(duì)列為空,等待
}
x = dequeue();//首節(jié)點(diǎn)出列
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
說明:略。承边。。
小結(jié)
本章只是為了加深同學(xué)們的印象富岳,為之后線程池源碼解析做準(zhǔn)備,隨便看看就行了萝喘。