[java队列]——ConcurrentLinkedQueue
- ConcurrentLinkedQueue介绍
- ConcurrentLinkedQueue内部实现
- 基本属性
- 构造方法
- 入队
- 出队
- ConcurrentLinkedQueue总结
- 与LinkedBlockingQueue做比较
ConcurrentLinkedQueue介绍
前面介绍过[java队列]——LinkedBlockingQueue,它是一个可阻塞的线程安全的单链表实现的队列,这里介绍另一个相似又有区别的队列ConcurrentLinkedQueue,它有以下特点
- 非阻塞队列,不能用于线程池
- 线程安全,使用CAS+自旋保证
- 单链表实现
ConcurrentLinkedQueue内部实现
基本属性
public class ConcurrentLinkedDeque<E> extends AbstractCollection<E>implements Deque<E>, java.io.Serializable {
//头结点
private transient volatile Node<E> head;
//尾结点
private transient volatile Node<E> tail;
private static class Node<E> {
volatile E item;
volatile Node<E> next;
}
结论:
- 单链表结构
构造方法
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
public ConcurrentLinkedQueue(Collection<? extends E> c) {
Node<E> h = null, t = null;
//遍历结合,将元素添加到单链表中
for (E e : c) {
checkNotNull(e);
Node<E> newNode = new Node<E>(e);
if (h == null)
h = t = newNode;
else {
t.lazySetNext(newNode);
t = newNode;
}
}
if (h == null)
h = t = new Node<E>(null);
head = h;
tail = t;
}
结论:
- 无参构造,设置头尾指针,结点值为null
- 传入集合构造,生成初始链表
入队
入队有两个方法add(E e)和offer(E e),因为ConcurrentLinkedQueue是非阻塞队列,因此没有put和offer(timeout)方法。
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
checkNotNull(e);
final Node<E> newNode = new Node<E>(e);
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) {
//判断q为null,则是待插入的尾结点位置
if (p.casNext(null, newNode)) {
//使用cas设置原尾结点的next指向新结点
if (p != t) // hop two nodes at a time
//通过cas设置好p->next指向新结点后,如果当前p不是原尾结点t,则说明有其他线程已经调整修改了尾结点
//这时候p并不是原尾结点t,因此要重新这是尾结点t。
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
//如果p->next = p,说明p已经出队。因此重新设置p得值
p = (t != (t = tail)) ? t : head;
else
// t后面还有值,重新设置p的值(这里说明有其他线程入队了)
p = (p != t && t != (t = tail)) ? t : q;
}
}
结论:
- 通过tail定位到队列的尾部,通过CAS把新结点设置到原尾结点的next结点。
- 如果在设置完之后,更新tail指向新结点。
出队
与入队一样,ConcurrentLinkedQueue出队一样只有两个方法remove和poll
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
//通过cas取出头结点,并更新头结点
if (p != h) // hop two nodes at a time
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
结论:
- 通过head定位到头节点,CAS更新其值为null;
- 如果CAS更新失败或者头节点变化了,就重新寻找头节点,并重试;
- 使用CAS和自旋出队的时候不会阻塞线程,没找到元素就返回null;
ConcurrentLinkedQueue总结
- 非阻塞队列
- 线程安全,使用CAS+自旋控制队列操作
- 不能用于线程池
与LinkedBlockingQueue做比较
队列 | 线程安全 | 阻塞 | 锁 | 线程池 | 有界 |
ConcurrentLinkedQueue | 安全 | 非阻塞 | 无锁,效率较高,使用CAS+自旋 | 不支持 | 有界 |
LinkedBlockingQueue | 安全 | 阻塞 | 重入锁,效率较低 | 支持 | 有界 |