原理
本质是Queue的实现类,用于多线程的容器,底层使用的是链表。(数据存储其实本质只有两种:连续的和非连续的,及链表和数字,为什么没有ConcurrentArrayQueue,因为数组的大小是固定的,在此基础上进行cas很麻烦)
Offer 添加方法
// 添加任务到队列尾部
public boolean offer(E e) {
//1检查是否非空
checkNotNull(e);
final Node<E> newNode = new Node<E>(e);
//无限循环,获取尾结点t
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
//初始化队列一开始的时候next节点为null
if (q == null) {
//使用cas插入节点
if (p.casNext(null, newNode)) {
// 当队列循环遍历发现尾结点和一开始取的尾节点不一致时,将最新插入的节点设置为尾结点。
//一次移动两个节点,防止操作过于频繁
if (p != t)
casTail(t, newNode);
return true;
}
}
else if (p == q) //当移除节点时会将next指向自己(哨兵节点),这时就要从头结点依次向后找到尾节点
p = (t != (t = tail)) ? t : head;
else
// cas多重检测并依次的next找到尾结点
p = (p != t && t != (t = tail)) ? t : q;
}
}
总结:offer的本质就是往链表的尾节点加数据,核心还是cas
1.检测数据是否为空
2.无线for循环,保证数据一定被插入,q = tail.next
3.如果q == null 说明当前节点就是尾结点,使用casNext将新Node加入尾。
if (p != t)casTail(t, newNode);每2个节点更新下Tail节点(防止频繁CAS)
else if (p == q) 当前节点是哨兵节点(poll时会让next指向自己),无法从尾部找,就从头节点往后找
else p = (p != t && t != (t = tail)) ? t : q; 获取最新的尾节点,如果不同就用最新的,相同就变为nextNode
Poll移除
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
//当item!=null,将item取出
if (item != null && p.casItem(item, null)) {
//没2次更新头节点
if (p != h)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
//当前节点的下一个节点是null,表示已经到了尾节点
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
//哨兵节点,从头再来
else if (p == q)
continue restartFromHead;
//获取NextNode
else
p = q;
}
}
}
final void updateHead(Node<E> h, Node<E> p) {
//将head更新为P,原来的h.nextNode指向自己
if (h != p && casHead(h, p))
h.lazySetNext(h);
}
void lazySetNext(Node<E> val) {
// 存储变量的引用到对象的指定的偏移量处
// 有延迟,非立即可见
UNSAFE.putOrderedObject(this, nextOffset, val);
}
总结:poll 从头节点移除
1.先从头节点获取item,如果不是null,尝试cas修改,头节点每2次更新
2.如果是null判断下一个节点是否是null,如果是表示到了末尾,return null
3.如果是哨兵节点就从头再来
4.一般情况下如果 item==null,获取当前节点的下一个节点