ConcurrentLinkedQueue閱讀筆記
一、簡介
一種同步的鏈表隊列,採用的自旋+CAS原子更新頭尾節點控制出隊入隊操作
二、繼承關係圖
三、存儲結構
採用鏈表的數據結構,數據更新和取出採用自旋+CAS原子操作
四、源碼分析
內部類
- Node:也是數據存儲的對象,也是鏈表的節點,包含當前節點值和下一個節點
private static class Node<E> {
volatile E item;//元素值
volatile Node<E> next;//下一個節點
/**
* Constructs a new node. Uses relaxed write because item can
* only be seen after publication via casNext.
*/
// 構造方法
Node(E item) {
// 原子操作初始化item,在指定對象的偏移量寫入一個值
UNSAFE.putObject(this, itemOffset, item);
}
// 更新item值
boolean casItem(E cmp, E val) {
//CAS原子更新當前節點的item值,cmp代表當前節點item值,val代表期望值
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
// 設置當前節點next的值
void lazySetNext(Node<E> val) {
//有序寫入val到當前節點的next中,不保證可見性,但是保證有序性,也就是保證指令不會被重排
UNSAFE.putOrderedObject(this, nextOffset, val);
}
// CAS原子更新
boolean casNext(Node<E> cmp, Node<E> val) {
//CAS原子更新當前節點的next值,cmp是當前節點next值,val是期望值
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
// Unsafe mechanics
/** 原子操作相關屬性和static初始化 */
private static final sun.misc.Unsafe UNSAFE;
private static final long itemOffset;
private static final long nextOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = Node.class;
itemOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("item"));
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
屬性
/** 頭結點 */
private transient volatile Node<E> head;
/** 尾節點 */
private transient volatile Node<E> tail;
/** Unsafe原子工具類,head、tail在對象中的偏移量 */
private static final sun.misc.Unsafe UNSAFE;
private static final long headOffset;
private static final long tailOffset;
構造
/** 構造方法一:無參構造 */
public ConcurrentLinkedQueue() {
// 初始化head、tail節點 都爲`new Node<E>(null)`且相同
head = tail = new Node<E>(null);
}
/** 構造方法二:使用集合初始化節點 */
public ConcurrentLinkedQueue(Collection<? extends E> c) {
// h:臨時頭節點
// t:臨時尾節點
Node<E> h = null, t = null;
// 循環遍歷集合c
// 把集合中元素生成Node對象
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (h == null)
// 如果h==null,代表第一個節點,直接賦值給h和t
h = t = newNode;
else {
// 如果h!=null,說明已經有頭節點了,
// 設置t節點next值
t.lazySetNext(newNode);
// 把t節點設置爲新的臨時尾節點
t = newNode;
}
}
if (h == null)
// 說明初始化集合參數沒有元素,直接設置臨時頭節點h和臨時尾巴節點t爲`new Node<E>(null)`
h = t = new Node<E>(null);
// 把臨時頭節點 h 賦予給head
// 把臨時尾節點 t 賦予給tail
head = h;
tail = t;
}
主要方法
1、入隊操作
- add(E e):完全等同於offer(E e);
- offer(E e):添加元素,自旋+CAS進行元素添加,如果添加成功true
/** 添加元素,完全等同於offer(E e) */
public boolean add(E e) {
// 如果鏈表只要內存足夠是不存在元素空間問題,所以直接調用offer即可
return offer(e);
}
/**
添加元素,自旋+CAS進行元素添加,如果添加成功true,
- 定位到鏈表尾節點,嘗試把新節點放到後面
- 如果尾部變化了,則重新拉去尾節點,再重試,直到成功
*/
public boolean offer(E e) {
// 效驗元素,爲null 拋出異常
checkNotNull(e);
// 生成待添加節點
final Node<E> newNode = new Node<E>(e);
// 循環:避免其他線程已經更新了尾節點
// t:緩存 尾節點
// p:第一次把 t 賦值給p,後續p會持續變化
// q:p的next節點值,如果爲null 則進入CAS更新
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
// 說明是尾節點的next,直接CAS更新p的next值
// 如果成功了,就返回true
// 如果不成功就重新取p.next重新嘗試
// p is last node
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
// 如果 p 不等於最後一次獲取的尾節點 ,說明有其他線程先一步更新了尾節點
// 所以,p有做更新,就開始CAS更新tail尾節點
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
// 返回入隊成功
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
// 如果p的next 等於p,說明p節點已經被刪除了
// 重新設置 t = tail
// 重新設置 p,
// 如果原 t 和新獲取tail的 t 不同,說明t有更新,直接用新的t作爲p節點
// 如果原 t 和新獲取tail的 t 相同,說明沒有節點(可能被出隊了)
p = (t != (t = tail)) ? t : head;
else
// Check for tail updates after two hops.
// t 後面還有值
// 重新設置 t = tail
// 重新設置 p 的值
p = (p != t && t != (t = tail)) ? t : q;
}
}
2、出隊操作
- 定位到頭節點,嘗試更新其值爲null
- 如果成功了,就成功出隊
- 如果失敗了或者頭結點變化了,就重新尋找頭結點,並重試
- 整個出隊過程沒有一點阻塞相關的代碼,所以出隊的時候不會阻塞線程,沒找到元素就返回null
public E remove() {
E x = poll();
if (x != null)
// 返回刪除後的元素
return x;
else
// 如果隊列沒有元素,則拋出異常
throw new NoSuchElementException();
}
// 彈出頭元素,成功返回彈出元素,沒有就返回null
public E poll() {
// 設置標記,用於控制剁成嵌套
restartFromHead:
for (;;) {
// 嘗試彈出頭節點
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
// Successful CAS is the linearization point
// for item to be removed from this queue.
// 如果 item 不等於null
// 並且 catItem更新爲null 成功
// 這個分支只有搶佔到出隊權限的全可以進來
if (p != h) // hop two nodes at a time
// 說明之前有其他線程先取出過一次節點,p有變化了,
// 調用updateHead 把head更新爲新q節點
// 如果(q = p.next) == null 說明沒有節點了,
// 那麼直接更新爲p,也就是沒變化,因爲p等於h
updateHead(h, ((q = p.next) != null) ? q : p);
// 返回出隊的元素值
return item;
}
// 下面三個分支說明頭節點變了
// 且 p 的 item 肯定爲null
else if ((q = p.next) == null) {
// 如果p 的next 爲空,說明隊列中沒有元素了
// 更新h 爲p,也就是空元素的節點
updateHead(h, p);
// 返回null
return null;
}
else if (p == q)
// 如果p 等於 p 的next,說明 p 已經出隊了,重試
// 標記之後是loop,纔可以用continue 標記名
// 跳過這個循環
continue restartFromHead;
else
// 將 p 設置爲 p 的next (上面q = p.next)
p = q;
}
}
}
/** 獲得隊列頭節點,如果有值返回值,沒有返回null */
public E peek() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null || (q = p.next) == null) {
// 說明可能item 不等於null,
// 也可能沒有節點
updateHead(h, p);
// 返回獲得的節點,
return item;
}
// 下面2個分支說明頭節點變化了
// 且p 的 item肯定爲null
else if (p == q)
// 說明 p 已經出列了,重試
continue restartFromHead;
else
// 將 p 設置爲 p 的next (上面q = p.next)
p = q;
}
}
}
/** 更新頭結點 */
final void updateHead(Node<E> h, Node<E> p) {
// 原子更新h爲p成功後,延遲更新h的next爲它自己
// 這裏用延遲更新是安全的,因爲head節點已經變了
// 只要入隊出隊的時候檢查head有沒有變化就行了,跟它的next關係不大
if (h != p && casHead(h, p))
h.lazySetNext(h);
}
3、移除指定節點
- 找元素然後去刪除,如果在過程自己(或其他線程已經)刪除了元素,他會幫忙重新關聯節點信息(移除被刪除的節點)
public boolean remove(Object o) {
// 如果 o 不爲null 則走移除邏輯
if (o != null) {
Node<E> next, pred = null;
for (Node<E> p = first(); p != null; pred = p, p = next) {
boolean removed = false;//刪除是否成功標記
E item = p.item;//緩存p.item
if (item != null) {
// 說明當前 p 是有值的,節點是存在的
if (!o.equals(item)) {
// 說明次節點不是需要刪除的對象,
// 那麼就直接獲取下一個值(此次就是p.next)
// succ(E e):獲取下一個節點,如果爲e 和e.next 相同則返回head
next = succ(p);
// 注意跳過後 會執行:pred = p, p = next
continue;
}
// 走到這裏說明當前節點的item和需要刪除的值 相同
// 嘗試更新 刪除當前節點p的item值
removed = p.casItem(item, null);
}
// 走到這裏,只有2個可能,一個是item==null,或者p的item已經cas爲null
// 所以,item 一定是 null
// 設置next 爲 p的獲取下一個值(此次就是p.next)
// succ(E e):獲取下一個節點,如果爲e 和e.next 相同則返回head
next = succ(p);
if (pred != null && next != null) // unlink
// 說明p元素已經被刪除了,或者被其他線程刪除了
// 直接跳過p 把pred 的next 關聯上next
// 如:prev -> P -> next 變爲 prev -> next
pred.casNext(p, next);
if (removed)
//如果removed = true,說明已經刪除成功。
return true;
}
}
// 循環所有節點沒有找到(或已經被其他線程移除了),移除失敗
return false;
}
補充
累死了,還補充啥
五、總結
不是阻塞隊列,採用CAS+自旋更新頭尾節點來控制出隊和入隊操作