ConcurrentLinkedQueue详解
ConcurrentLinkedQueue详解
简介
实现线程安全的队列的两种方式:
- 使用阻塞算法:对入队和出队使用同一把锁(或不同的锁).
- 使用非阻塞算法:使用循环CAS方式实现.
- ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列.
- 采用先进先出规则对节点排序.
- 添加元素会添加到队列的尾部,获取元素会返回队列头部元素.
结构
- 有两个结点:头节点(head)和尾节点(tail).
- 每个节点由结点元素(item)和指向下一个节点的指针(next)组成.
- 默认情况下head节点存储的元素为空,tail节点等于head节点.
操作
入队列
将入队节点添加到队列尾部.
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) {
if (p.casNext(null, newNode)) {
if (p != t)
casTail(t, newNode);
return true;
}
}
else if (p == q)
p = (t != (t = tail)) ? t : head;
else
p = (p != t && t != (t = tail)) ? t : q;
}
}
流程:
- 定位尾节点.
- 使用CAS将入队节点设置为尾节点的next节点,若不成功,则重试.
定位尾节点:
tail节点并非总是尾节点.每次入队必须先通过tail节点查找尾节点.
设置入队节点为尾节点:
设置入队节点为当前尾节点的next节点.
- 刚开始head和tail节点都指向空节点.
- 添加一个节点后,head和tail仍然指向空节点.
- 再添加节点时,head指向空节点,tail指向尾节点.
- 再次添加节点时,tail保持不变.
- 再添加结点时,tail指向尾节点.
出队列
出队列是从队列中返回一个节点元素.
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
if (p != h)
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节点有元素时,直接弹出head节点对应的元素,不更新head节点.
- 当head节点没有元素时,出队操作更新head节点.
目的:减少使用CAS更新head节点的消耗,提高效率.
- 先获取头节点的元素.
- 再判断头节点元素是否为空.如果为空,则另外一个线程已进行出队操作.
- 若不为空,则使用CAS方式将头节点的引用设为null.成功则直接返回头节点元素;不成功则其他线程已进行出队操作更新head节点,需重新获取头节点.
参考:
本文来自博客园,作者:战五渣渣渣渣渣,转载请注明原文链接:https://www.cnblogs.com/truestoriesavici01/p/13214038.html