问题

(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源码分析的更多相关文章

  1. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

  2. 死磕 java集合之PriorityBlockingQueue源码分析

    问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...

  3. 死磕 java集合之PriorityQueue源码分析

    问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...

  4. 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计

    问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...

  5. 死磕 java集合之LinkedHashSet源码分析

    问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...

  6. 死磕 java集合之ConcurrentHashMap源码分析(三)

    本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...

  7. 死磕 java集合之ArrayDeque源码分析

    问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...

  8. 【死磕 Java 集合】— ConcurrentSkipListMap源码分析

    转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...

  9. 死磕 java集合之LinkedList源码分析

    问题 (1)LinkedList只是一个List吗? (2)LinkedList还有其它什么特性吗? (3)LinkedList为啥经常拿出来跟ArrayList比较? (4)我为什么把LinkedL ...

随机推荐

  1. swagger在nginx下出现无法请求接口的问题

    在Nginx配置绑定域名的时候,增加proxy_set_header即可 示例如下: server { listen ; server_name xxx.dev.internal.XXX.com; l ...

  2. 让 Homebrew 走代理更新 + brew 管理 node 版本

    0.前言 环境:MacOS 背景:整理下今天所做的配置. 1. 让 Homebrew 走代理更新 brew update 就卡住了,即使开了 shadowsocks 也不行.因为 shadowsock ...

  3. Sass快速入门学习笔记

    1. 使用变量; sass让人们受益的一个重要特性就是它为css引入了变量.你可以把反复使用的css属性值 定义成变量,然后通过变量名来引用它们,而无需重复书写这一属性值.或者,对于仅使用过一 次的属 ...

  4. DOM4J熟知

    什么是解析xml 系统最终会从xml中读取数据. 读取的过程就是解析. CRUD ==> 增删改查 ==> create read update delete ==> 解析指的就是读 ...

  5. Jenkins配置报告与邮件插件

    用jenkins做持续集成时,需要发送报告与邮件,下面说一下如何配置报告与邮件的插件 1:配置报告插件 我们先装一个Report插件,在系统管理-管理插件中找  HTML Publisher plug ...

  6. Windows驱动开发入门指引

       1.  前言 因工作上项目的需要,笔者需要做驱动相关的开发,之前并没有接触过相关的知识,折腾一段时间下来,功能如需实现了,也积累了一些经验和看法,所以在此做番总结. 对于驱动开发的开发指引,微软 ...

  7. 谈论seo思维性对优化中起到决定性的作用

    在<SEO的艺术>又出版之后,SEO艺术更加受到了广大SEOer的关注和热捧,在这本书里面,也有很多的不为人知的技巧分享.SEO的艺术强调的是SEO融入网络营销,融入社会化媒体大潮,然而这 ...

  8. Reflection的getCallerClass静态方法

    Reflection的getCallerClass的使用 博客分类: java基础   Reflection的getCallerClass的使用:可以得到调用者的类.这个方法是很好用的. 0 和小于0 ...

  9. java基础-学java util类库总结

    JAVA基础 Util包介绍 学Java基础的工具类库java.util包.在这个包中,Java提供了一些实用的方法和数据结构.本章介绍Java的实用工具类库java.util包.在这个包中,Java ...

  10. 【C#】对异步请求处理程序IHttpAsyncHandler的理解和分享一个易用性封装

    在asp.net项目中,添加一个[一般处理程序]来处理请求是很自然的事,这样会得到一个实现自IHttpHandler的类,然后只需在ProcessRequest方法中写上处理逻辑就行了.但是这样的一个 ...