问题

(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)前者无法用在线程池中,后者可以;

集合-ConcurrentLinkedQueue 源码解析的更多相关文章

  1. Java集合-ArrayList源码解析-JDK1.8

    ◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...

  2. 集合-ArrayList 源码解析

    ArrayList是一种以数组实现的List,与数组相比,它具有动态扩展的能力,因此也可称之为动态数组. 类图 ArrayList实现了List, RandomAccess, Cloneable, j ...

  3. Java并发包源码学习系列:基于CAS非阻塞并发队列ConcurrentLinkedQueue源码解析

    目录 非阻塞并发队列ConcurrentLinkedQueue概述 结构组成 基本不变式 head的不变式与可变式 tail的不变式与可变式 offer操作 源码解析 图解offer操作 JDK1.6 ...

  4. Java集合---LinkedList源码解析

    一.源码解析1. LinkedList类定义2.LinkedList数据结构原理3.私有属性4.构造方法5.元素添加add()及原理6.删除数据remove()7.数据获取get()8.数据复制clo ...

  5. ava集合---LinkedList源码解析

    一.源码解析 public class LinkedList<E> extends AbstractSequentialList<E> implements List<E ...

  6. 《java入门第一季》之集合toString源码解析

    代码: Collection c = new ArrayList(); c.add("hello"); c.add("world"); c.add(" ...

  7. java集合-HashSet源码解析

    HashSet 无序集合类 实现了Set接口 内部通过HashMap实现 // HashSet public class HashSet<E> extends AbstractSet< ...

  8. java集合-HashMap源码解析

    HashMap 键值对集合 实现原理: HashMap 是基于数组 + 链表实现的. 通过hash值计算 数组索引,将键值对存到该数组中. 如果多个元素hash值相同,通过链表关联,再头部插入新添加的 ...

  9. java集合类型源码解析之PriorityQueue

    本来第二篇想解析一下LinkedList,不过扫了一下源码后,觉得LinkedList的实现比较简单,没有什么意思,于是移步PriorityQueue. PriorityQueue通过数组实现了一个堆 ...

随机推荐

  1. 用头发换JS的第一天

    因为平时也在上班,这些都是在地铁上抽空开始看的,总结来说还是有一些收获存在,每次看都能发现一些新东西,废话不多说,开撸! JavaScript是一种网页编程语言,可以对网页的行为进行编程,简单来说HT ...

  2. 非线性规划的Matlab 解法

    编写M 文件fun1.m 定义目标函数 function f=fun1(x); % 定义目标函数 f=sum(x.^)+; % .^2是矩阵中的每个元素都求平方.^2是求矩阵的平方或两个相同的矩阵相乘 ...

  3. c常用函数-strchr和strrchr

    strchr和strrchr strrchr函数用于查找指定字符在一个字符串中最后一次出现的位置,然后返回指向该位置的指针 strchr函数用于查找指定字符在一个字符串中第一次出现的位置,然后返回指向 ...

  4. 01.DRF-Web应用模式

    Web应用模式 在开发Web应用中,有两种应用模式: 前后端不分离 前后端分离 1 前后端不分离 在前后端不分离的应用模式中,前端页面看到的效果都是由后端控制,由后端渲染页面或重定向,也就是后端需要控 ...

  5. 事务的特性ACID、隔离级别

    1.事务特性ACID 1.1 事务的四大特性 1.原子性(Atomicity) 事务包装的一组sql,要么都执行成功,要么都失败.这些操作是不可分割的. 2.一致性(Consistency) 数据库的 ...

  6. 本地代码提交到远程仓库(git)

    [准备环境] 我没有在Linux搭建gitlab私有云服务器,用的是开源的 gitee托管平台 1.在gitee注册账号 2.本地下载git客户端 [步骤] 1  本地新建1个文件夹  进入文件夹后 ...

  7. C语言实现类

    #ifndef __DEFINE__H__ #define __DEFINE__H__ #define vector3(type) \ typedef struct vector3_##type { ...

  8. Java前端面试题总结

    Java前端面试题总结 简单说一下HTML,CSS,javaScript在网页开发中的定位? HTML:超文本标记语言,定义网页的结构 CSS:层叠样式表,用来美化页面 JavaScript:主要用来 ...

  9. jquery-form详解

    jQuery-Form 概观 jQuery表单插件允许您轻松而不显眼地升级HTML表单以使用AJAX.主要方法ajaxForm和ajaxSubmit从表单元素收集信息以确定如何管理提交过程.这两种方法 ...

  10. Kali中密码暴力破解工具hydra的使用

    前言 hydra是著名黑客组织thc的一款开源的暴力破解密码工具,功能非常强大,kali下是默认安装的,几乎支持所有协议的在线破解.密码能否破解,在于字典是否强大.本文仅从安全角度去讲解工具的使用,请 ...