问题

(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. TypeError: this.xxx.substring is not a function的解决办法

    这是因为已经改变了xxx的值的类型,不再是字符串的话将不会拥有substring函数, 我当时这样写的时候,直接将number类型赋予了this.enter,所以导致了错误. 改为这样之后可以使用su ...

  2. map处理:map(str,[1,2,3,4,5,6])

    #map(s1,s2)传入两个参数,s1是对该Iterable每个参数做处理的参数,s2是该Iterable print(list(map(str,[1,2,3,4,5,6]))) #map()传入的 ...

  3. 厉害了!除了find命令,还有这么多文件查找命令,高手必备!

    大家好,我是良许. 在系统里查找文件,是所有工程师都必备的技能(不管你用的是 Windows .Linux.还是 MacOS 系统).对于 Linux 操作系统,单单一个 find 命令就可以完成非常 ...

  4. 这一次搞懂Spring的XML解析原理

    前言 Spring已经是我们Java Web开发必不可少的一个框架,其大大简化了我们的开发,提高了开发者的效率.同时,其源码对于开发者来说也是宝藏,从中我们可以学习到非常优秀的设计思想以及优雅的命名规 ...

  5. Rust 数据类型

    Rust中的每个值都具有特定的数据类型. 基础类型: 整数,浮点数,布尔值和字符 i8,i16,i32,i64,i64,i128,isize, u8,u16,u32,u64,u64,u128,usiz ...

  6. Flask框架基础功能

    引言 本文简单汇总Flask框架几大基础功能,包括: 路由系统 模板 数据库 几种常用Flask库 一个简单的Flask事例 Flask是一个基于Python,依赖Jinja2模板和WSGI服务的框架 ...

  7. 流媒体学习计划表——pr

    参考教程 视频:b站oeasy 书籍:<adobe premiere pro cc 2018经典教程> 学习教训 一定要多做--实践是检验真理的唯一标准 书籍补充理论知识,视频讲究实操(理 ...

  8. laravel生成key失败

    laravel生成key失败 生成KEY失败.原因是没有复制.env文件 In KeyGenerateCommand.php line 96: file_get_contents(D:\project ...

  9. MySQL8.0窗口函数实践及小结

    MySQL8.0之前,做数据排名统计等相当痛苦,因为没有像Oracle.SQL SERVER .PostgreSQL等其他数据库那样的窗口函数.但随着MySQL8.0中新增了窗口函数之后,针对这类统计 ...

  10. pikachu靶场-暴力破解(验证码、token)

    甲.基于表单的破解 较为简单,直接BurpSuite爆破. 乙.验证码绕过(on server) 打开题目,比第一题多了一个验证码验证,很多初学者遇到验证码就会感觉不知所措.其实这题也较为简单,首先正 ...