JDK在1.5以后由Doug Lea为我们提供了ConcurrentLinkedQueue这个线程安全的队列。数据结构是链表的实现。这里我们通过分析java中的ConcurrentLinkedQueue的实现来理解并发队列。
结构定义
在ConcurrentLinkedQueue中,所有的记录通过其内部的Node结构进行包装。并维护着二个由volatile关键字修饰的队列头尾节点.head/tail.
//java中实现并发队列的实现
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, Serializable {
//队列的头部节点.
private transient volatile Node<E> head;
//队列的尾部节点.队列中唯一一个node.next可以是null的节点.
private transient volatile Node<E> tail;
//初始化队列,生成head,tail的哨兵节点,他们的next都是null.
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
//队列存储记录的节点数据结构,基于CAS实现的线程安全.
private static class Node<E> {
volatile E item; //记录值
volatile Node<E> next; //当前节点的下一节点指针
//初始化
Node(E item) {
UNSAFE.putObject(this, itemOffset, item);
}
//更新节点值,
boolean casItem(E cmp, E val) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
}
//延时设置node的next指针,在更新表头时,让node.next指向node自己.
void lazySetNext(Node<E> val) {
UNSAFE.putOrderedObject(this, nextOffset, val);
}
//更新节点的next指针.
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}
}
插入数据(offer)
在ConcurrentLinkedQueue并发队列的实现中插入操作通过CAS自旋来实现,向队列插入从队列的尾部tail开始,插入数据可以分为三种场景:单线程插入、多线程插入与多线程插入读取并行,在并发的场景下tail可能不是真实的尾节点,因此插入时要不断迭代来确保是在真实的尾节点后面插入数据(并发队列每插入两个节点更新一次tail)。
//并发队列的入队实现,这里有三种情况,单线程插入,多线程插入,多线程同时插入与读取
public boolean offer(E e) {
//1,并发队列中的记录值不能是null.
checkNotNull(e);
//根据记录值生成队列中存储的节点.
final Node<E> newNode = ne