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+自旋更新头尾节点来控制出队和入队操作