這個(gè)小節(jié)介紹Queue的最后一個(gè)工具,也是最強(qiáng)大的一個(gè)工具。從名稱上就可以看到此工具的特點(diǎn):雙向并發(fā)阻塞隊(duì)列。所謂雙向是指可以從隊(duì)列的頭和尾同時(shí)操作,并發(fā)只是線程安全的實(shí)現(xiàn),阻塞允許在入隊(duì)出隊(duì)不滿足條件時(shí)掛起線程,這里說的隊(duì)列是指支持FIFO/FILO實(shí)現(xiàn)的鏈表。
首先看下LinkedBlockingDeque的數(shù)據(jù)結(jié)構(gòu)。通常情況下從數(shù)據(jù)結(jié)構(gòu)上就能看出這種實(shí)現(xiàn)的優(yōu)缺點(diǎn),這樣就知道如何更好的使用工具了。
從數(shù)據(jù)結(jié)構(gòu)和功能需求上可以得到以下結(jié)論:
- 要想支持阻塞功能,隊(duì)列的容量一定是固定的,否則無法在入隊(duì)的時(shí)候掛起線程。也就是capacity是final類型的。
- 既然是雙向鏈表,每一個(gè)結(jié)點(diǎn)就需要前后兩個(gè)引用,這樣才能將所有元素串聯(lián)起來,支持雙向遍歷。也即需要prev/next兩個(gè)引用。
- 雙向鏈表需要頭尾同時(shí)操作,所以需要first/last兩個(gè)節(jié)點(diǎn),當(dāng)然可以參考LinkedList那樣采用一個(gè)節(jié)點(diǎn)的雙向來完成,那樣實(shí)現(xiàn)起來就稍微麻煩點(diǎn)。
- 既然要支持阻塞功能,就需要鎖和條件變量來掛起線程。這里使用一個(gè)鎖兩個(gè)條件變量來完成此功能。
有了上面的結(jié)論再來研究LinkedBlockingDeque的優(yōu)缺點(diǎn)。
優(yōu)點(diǎn)當(dāng)然是功能足夠強(qiáng)大,同時(shí)由于采用一個(gè)獨(dú)占鎖,因此實(shí)現(xiàn)起來也比較簡單。所有對隊(duì)列的操作都加鎖就可以完成。同時(shí)獨(dú)占鎖也能夠很好的支持雙向阻塞的特性。
凡事有利必有弊。缺點(diǎn)就是由于獨(dú)占鎖,所以不能同時(shí)進(jìn)行兩個(gè)操作,這樣性能上就大打折扣。從性能的角度講LinkedBlockingDeque要比LinkedBlockingQueue要低很多,比CocurrentLinkedQueue就低更多了,這在高并發(fā)情況下就比較明顯了。
前面分析足夠多的Queue實(shí)現(xiàn)后,LinkedBlockingDeque的原理和實(shí)現(xiàn)就不值得一提了,無非是在獨(dú)占鎖下對一個(gè)鏈表的普通操作。
有趣的是此類支持序列化,但是Node并不支持序列化,因此fist/last就不能序列化,那么如何完成序列化/反序列化過程呢?
清單1 LinkedBlockingDeque的序列化、反序列化
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
lock.lock();
try {
// Write out capacity and any hidden stuff
s.defaultWriteObject();
// Write out all elements in the proper order.
for (Node<E> p = first; p != null; p = p.next)
s.writeObject(p.item);
// Use trailing null as sentinel
s.writeObject(null);
} finally {
lock.unlock();
}
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
count = 0;
first = null;
last = null;
// Read in all elements and place in queue
for (;;) {
E item = (E)s.readObject();
if (item == null)
break;
add(item);
}
}
清單1 描述的是LinkedBlockingDeque序列化/反序列化的過程。序列化時(shí)將真正的元素寫入輸出流,最后還寫入了一個(gè)null。讀取的時(shí)候?qū)⑺袑ο罅斜碜x出來,如果讀取到一個(gè)null就表示結(jié)束。這就是為什么寫入的時(shí)候?qū)懭胍粋€(gè)null的原因,因?yàn)闆]有將count寫入流,所以就靠null來表示結(jié)束,省一個(gè)整數(shù)空間。
©2009-2014 IMXYLZ
|求賢若渴