實(shí)現(xiàn)并發(fā)安全有兩種方式:一種是阻塞式的:例如:LinkedBlockingQueue;另一種是非阻塞式的:例如:ConcurrentLinkedQueue,非阻塞式的最顯著的優(yōu)點(diǎn)是性能,非阻塞式算法使用CAS原子性來更新數(shù)據(jù)忠藤。避免了加鎖的時(shí)間,同時(shí)也保證了數(shù)據(jù)的一致性。
1.ConcurrentLinkedQueue簡介
ConcurrentLinkedQueue中包含兩個(gè)內(nèi)部類席舍,Node<E>和Itr稼虎。Node<E>表示ConcurrentLinkedQueue鏈表中的一個(gè)節(jié)點(diǎn)孔飒;Itr類用來遍歷ConcurrentLinkedQueue。
2.常用方法
2.1offer()
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
//入隊(duì)前骤公,創(chuàng)建一個(gè)入隊(duì)節(jié)點(diǎn)
Node<E> n = new Node<E>(e);
retry:
//死循環(huán),入隊(duì)不成功反復(fù)入隊(duì)扬跋。
for (;;) {
//創(chuàng)建一個(gè)指向tail節(jié)點(diǎn)的引用
Node<E> t = tail;
//p用來表示隊(duì)列的尾節(jié)點(diǎn)阶捆,默認(rèn)情況下等于tail節(jié)點(diǎn)。
Node<E> p = t;
for (int hops = 0; ; hops++) {
//獲得p節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)钦听。
Node<E> next = succ(p);
//next節(jié)點(diǎn)不為空洒试,說明p不是尾節(jié)點(diǎn),需要更新p后在將它指向next節(jié)點(diǎn)
if (next != null) {
//循環(huán)了兩次及其以上朴上,并且當(dāng)前節(jié)點(diǎn)還是不等于尾節(jié)點(diǎn)
if (hops > HOPS && t != tail)
continue retry;
p = next;
}
//如果p是尾節(jié)點(diǎn)垒棋,則設(shè)置p節(jié)點(diǎn)的next節(jié)點(diǎn)為入隊(duì)節(jié)點(diǎn)。
else if (p.casNext(null, n)) {
//如果tail節(jié)點(diǎn)有大于等于1個(gè)next節(jié)點(diǎn)痪宰,則將入隊(duì)節(jié)點(diǎn)設(shè)置成tair節(jié)點(diǎn)叼架,更新失敗了也
沒關(guān)系,因?yàn)槭×吮硎居衅渌€程成功更新了tair節(jié)點(diǎn)衣撬。
if (hops >= HOPS)
casTail(t, n); // 更新tail節(jié)點(diǎn)乖订,允許失敗
return true;
}
// p有next節(jié)點(diǎn),表示p的next節(jié)點(diǎn)是尾節(jié)點(diǎn),則重新設(shè)置p節(jié)點(diǎn)
else {
p = succ(p);
}
}
}
}
上面代碼的設(shè)計(jì)原理有兩點(diǎn)具练,1.定位出尾節(jié)點(diǎn)垢粮;2.使用CAS將入隊(duì)節(jié)點(diǎn)設(shè)置成尾節(jié)點(diǎn)的next節(jié)點(diǎn)。
2.2poll
public E poll() {
Node<E> h = head;
// p表示頭節(jié)點(diǎn)靠粪,需要出隊(duì)的節(jié)點(diǎn)
Node<E> p = h;
for (int hops = 0;; hops++) {
// 獲取p節(jié)點(diǎn)的元素
E item = p.getItem();
// 如果p節(jié)點(diǎn)的元素不為空蜡吧,使用CAS設(shè)置p節(jié)點(diǎn)引用的元素為null,如果成功則返回p節(jié)點(diǎn)的元素。
if (item != null && p.casItem(item, null)) {
if (hops >= HOPS) {
//將p節(jié)點(diǎn)下一個(gè)節(jié)點(diǎn)設(shè)置成head節(jié)點(diǎn)
Node<E> q = p.getNext();
updateHead(h, (q != null) ? q : p);
}
return item;
}
// 如果頭節(jié)點(diǎn)的元素為空或頭節(jié)點(diǎn)發(fā)生了變化占键,這說明頭節(jié)點(diǎn)已經(jīng)被另外一個(gè)線程修改了昔善。那么獲取p節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)
Node<> next = succ(p);
// 如果p的下一個(gè)節(jié)點(diǎn)也為空,說明這個(gè)隊(duì)列已經(jīng)空了
if (next == null) {
// 更新頭節(jié)點(diǎn)畔乙。
updateHead(h, p);
break;
}
// 如果下一個(gè)元素不為空君仆,則將頭節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)設(shè)置成頭節(jié)點(diǎn)
p = next;
}
return null;
}
首先獲取頭結(jié)點(diǎn)的值,然后判斷頭結(jié)點(diǎn)元素是否為空,如果為空返咱,就表示另一個(gè)線程已經(jīng)進(jìn)行了一次出隊(duì)的操作钥庇,如果不為空,則使用CAS將頭結(jié)點(diǎn)的引用設(shè)置為空咖摹,如果CAS成功评姨,直接返回頭結(jié)點(diǎn)的元素,如果不成功萤晴,另外一個(gè)線程已經(jīng)完成了出隊(duì)操作并跟新了head元素吐句,,需要重新獲取頭結(jié)點(diǎn)店读。
2.3remove
public boolean remove(Object o) {
if (o == null) return false;
Node<E> pred = null;
// 這里是從head 開始的嗦枢,中間還涉及到head 的判斷等從操作
// 跟一般for 循環(huán)類似,要遍歷的- -屯断,這樣的操作o 很靠后的時(shí)候文虏,會(huì)慢- -!
// - -不太喜歡這方法殖演,了解作用
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.item;
if (item != null &&
o.equals(item) &&
p.casItem(item, null)) {
Node<E> next = succ(p);
if (pred != null && next != null)
pred.casNext(p, next);
return true;
}
pred = p;
}
return false;
}