線程安全的隊列
- 阻塞隊列:使用一個鎖(進隊和出隊同一個鎖)和兩個鎖(入隊和出隊用不同的鎖)
- 非阻塞隊列:ConcurrentLinkedQueue
ConcurrentLinkedQueue特性:
- 是一個基於鏈接節點的無界線程安全隊列,
- 按先進先出的插入順序進行排序,
- 採用“wait-free”算法(CAS算法)實現,
- 具體實現:添加元素時它會添加到隊列尾部;獲取元素時,它會返回隊列頭部的元素。
1.8 source code
https://blog.csdn.net/chenssy/article/details/74853120
1、入隊方法
public boolean offer(E e) {
//判斷入隊節點是否爲空
checkNotNull(e);
//構造入隊節點
final Node<E> newNode = new Node<E>(e);
//死循環,入隊不成功繼續入隊
//p初始爲tail
for (Node<E> t = tail, p = t;;) {
//q爲p的後繼,若p爲隊尾,則q爲空,若p不是隊尾,則有其他線程更改了隊列
Node<E> q = p.next;
if (q == null) {
//p是隊尾 將入隊節點變成隊尾的後繼
if (p.casNext(null, newNode)) {
//判斷tail節點是否爲尾節點
if (p != t)
//如果不是,將入隊節點修改爲隊尾
casTail(t, newNode);
return true;
}
// Lost CAS race to another thread; re-read next
}
//說明這個節點已經被移除了
else if (p == q)
//如果隊尾未發生改變,t還是隊尾,則將p指向t,繼續插入新節點
//如果隊尾發生改變,則將p指向隊頭重新開始插入新節點
p = (t != (t = tail)) ? t : head;
else
//說明p有後繼,p的後繼纔是隊尾,則需要將p指向隊尾
p = (p != t && t != (t = tail)) ? t : q;
}
}
入隊方法個人感覺還是挺複雜的。大致步驟如下
1、構造新節點
2、開始循環,p表示隊尾,默認p初始爲tail,
3、如果p後繼爲空,說明p就是tail,則插入新節點返回
4、如果p節點被刪除,則需要重新賦值p,如果tail未變,則將p賦值爲t,如果tail改變,則將p賦值爲隊頭,繼續插入
5、如果p有後繼,說明p不是tail,則需要將p指向tail。
2出隊方法
public E poll() {
restartFromHead:
//死循環
for (;;) {
for (Node<E> h = head, p = h, q;;) {
//取出隊頭的值
E item = p.item;
//如果隊頭值不爲空則將隊頭的值更新爲空
if (item != null && p.casItem(item, null)) {
//判斷p是否等於head,如果不等於則重新指向隊頭
if (p != h)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
//隊列爲空
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
//有其他線程將隊頭的值出隊了,則重新開始出隊
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
出隊的大致步驟如下:
1、h等於head,p初始化爲head
2、取出隊頭的值
3、如果隊頭的值不爲空,則將隊頭的值置空,然後判斷head是否變化,如果變化重新尋找head,返回隊頭的值
4、如果隊列爲空,則需要重新尋找隊頭,返回null
5、如果有其他線程將隊頭的值出隊了,那麼重新從最新head開始出隊