死磕 java集合之ConcurrentLinkedQueue源码分析
问题
(1)ConcurrentLinkedQueue是阻塞队列吗?
(2)ConcurrentLinkedQueue如何保证并发安全?
(3)ConcurrentLinkedQueue能用于线程池吗?
简介

ConcurrentLinkedQueue只实现了Queue接口,并没有实现BlockingQueue接口,所以它不是阻塞队列,也不能用于线程池中,但是它是线程安全的,可用于多线程环境中。
那么,它的线程安全又是如何实现的呢?让我们一起来瞧一瞧。
源码分析
主要属性
// 链表头节点
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;
// 遍历c,并把它元素全部添加到单链表中
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;
}
这两个构造方法也很简单,可以看到这是一个无界的单链表实现的队列。
入队
因为它不是阻塞队列,所以只有两个入队的方法,add(e)和offer(e)。
因为是无界队列,所以add(e)方法也不用抛出异常了。
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;
// 如果没有next,说明到链表尾部了,就入队
if (q == null) {
// CAS更新p的next为新节点
// 如果成功了,就返回true
// 如果不成功就重新取next重新尝试
if (p.casNext(null, newNode)) {
// 如果p不等于t,说明有其它线程先一步更新tail
// 也就不会走到q==null这个分支了
// p取到的可能是t后面的值
// 把tail原子更新为新节点
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
// 返回入队成功
return true;
}
}
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;
}
}
入队整个流程还是比较清晰的,这里有个前提是出队时会把出队的那个节点的next设置为节点本身。
(1)定位到链表尾部,尝试把新节点放到后面;
(2)如果尾部变化了,则重新获取尾部,再重试;
出队
因为它不是阻塞队列,所以只有两个出队的方法,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;
// 如果节点的值不为空,并且将其更新为null成功了
if (item != null && p.casItem(item, null)) {
// 如果头节点变了,则不会走到这个分支
// 会先走下面的分支拿到新的头节点
// 这时候p就不等于h了,就更新头节点
// 在updateHead()中会把head更新为新节点
// 并让head的next指向其自己
if (p != h) // hop two nodes at a time
updateHead(h, ((q = p.next) != null) ? q : p);
// 上面的casItem()成功,就可以返回出队的元素了
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已经出队了,重试
continue restartFromHead;
else
// 将p设置为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);
}
出队的整个逻辑也是比较清晰的:
(1)定位到头节点,尝试更新其值为null;
(2)如果成功了,就成功出队;
(3)如果失败或者头节点变化了,就重新寻找头节点,并重试;
(4)整个出队过程没有一点阻塞相关的代码,所以出队的时候不会阻塞线程,没找到元素就返回null;
总结
(1)ConcurrentLinkedQueue不是阻塞队列;
(2)ConcurrentLinkedQueue不能用在线程池中;
(3)ConcurrentLinkedQueue使用(CAS+自旋)更新头尾节点控制出队入队操作;
彩蛋
ConcurrentLinkedQueue与LinkedBlockingQueue对比?
(1)两者都是线程安全的队列;
(2)两者都可以实现取元素时队列为空直接返回null,后者的poll()方法可以实现此功能;
(3)前者全程无锁,后者全部都是使用重入锁控制的;
(4)前者效率较高,后者效率较低;
(5)前者无法实现如果队列为空等待元素到来的操作;
(6)前者是非阻塞队列,后者是阻塞队列;
(7)前者无法用在线程池中,后者可以;
欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java集合之ConcurrentLinkedQueue源码分析的更多相关文章
- 死磕 java集合之DelayQueue源码分析
问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...
- 死磕 java集合之PriorityBlockingQueue源码分析
问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...
- 死磕 java集合之PriorityQueue源码分析
问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
- 死磕 java集合之LinkedHashSet源码分析
问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...
- 死磕 java集合之ConcurrentHashMap源码分析(三)
本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...
- 死磕 java集合之ArrayDeque源码分析
问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...
- 【死磕 Java 集合】— ConcurrentSkipListMap源码分析
转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...
- 死磕 java集合之LinkedList源码分析
问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...
随机推荐
- Nowcoder84D
Nowcoder84D 传送门 很有趣的进制转换题! 如果x满足题意,那么x+k-1一定能符合要求! 因为k-1用k进制表示就是1,-1,1+(-1)=0所以数位之和不变! 用map维护一下前缀和.就 ...
- java-将评论内容过滤特殊表情emoj符号,保存到mysql中
正常操作评论,保存时,若评论内容含有特殊表情符号,后台将报错如下: Illegal mix of collations (utf8_general_ci,IMPLICIT) and (utf8mb4_ ...
- MySQL-数据检索
MySQL简介 1.什么是数据库 ? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,它产生于距今六十多年前,随着信息技术和市场的发展,特别是二十世纪九十年代以后,数据管理不再仅 ...
- “蝉原则”与CSS3随机多背景随机圆角等效果
一.什么是“蝉原则”? “蝉原则”,英文称作“cicada principle”,是一种让事物的重复出现符合“自然随机性”的规则,为什么这么说呢? “蝉原则”源自于北美,中国似乎并未有这样的说法,这背 ...
- Robot Framework和Selenium简介
1.1 RF简介 Robot Framework是一款python编写的功能自动化测试框架.具备良好的可扩展性,支持关键字驱动,可以同时测试多种类型的客户端或者接口,可以进行分布式测试执行.主要用于 ...
- Code Review Checklist
左按:当年需要一份详细的代码评审清单作参考,翻译了此文. 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[-] General Code Smoke Test 通用测试 Comm ...
- 基于支付系统真实场景的分布式事务解决方案效果演示: http://www.iqiyi.com/w_19rsveqlhh.html
基于支付系统真实场景的分布式事务解决方案效果演示:http://www.iqiyi.com/w_19rsveqlhh.html
- C++相关:部分标准库特殊设施
C++ tuple(元组) tuple是C++11新标准里的类型.它是一个类似pair类型的模板.pair类型是每个成员变量各自可以是任意类型,但是只能有俩个成员,而tuple与pair不同的是它可以 ...
- javascript 正则(将数字转化为三位分隔的样式)
'12345678912345678'.replace(/\B(?=(?:\d{3})+\b)/g, ',') 解释: \b : 匹配单词边界,就是位于字符\w([a-zA-Z0-9_])和\W[^a ...
- SVN 使用方法
svn co http://路径(目录或文件的全路径) [本地目录全路径] --username 用户名 --password 密码svn co svn://路径(目录或文件的全路径) [本地目录全路 ...