ConcurrentLinkedQueue代码解析
原因:学习ConcurrentLinkedQueue是看到akka框架的默认邮箱是使用ConcurrentLinkedQueue实现的。
1. ConcurrentLinkedQueue在java.util.concurrent包中(java 版本是1.7.0_71),类间继承关系如下:
public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
implements Queue<E>, java.io.Serializable
ConcurrentLinkedQueue继承了抽象类AbstractQueue,AbstractQueue抽象类中的几个实现方法也都是利用Queue接口中的方法实现的。
Queue接口中定义的抽象方法有:
package java.util; public interface Queue<E> extends Collection<E> { // 向队列中插入元素e,不验证队列空间限制条件下插入一个元素。如果队列有剩余空间,直接插入;如果队列满了,就抛出IllegalStateException异常
boolean add(E e); // 同样是向队列中插入元素e。如果队列有空间限制,同add;如果队列没有空间限制,比如ConcurrentLinkedQueue,总是可以插入进去
boolean offer(E e); // 返回并删除队列头部的第一个元素,remove()与poll()方法的不同在于,如果队列为空,remove()方法会抛出异常,而poll()方法是返回null
E remove(); // 返回并删除队列头部的第一元素,如果队列空,返回null
E poll(); // 返回但是不删除队头元素,element()方法与peek()方法的不同在于,如果队列为空,element()方法会抛出NoSuchElementException,而peek()方法返回null
E element(); // 返回队头元素,如果队列为空,返回null
E peek();
}
队列的操作无非就是上述的插入和删除操作,从上述方法的定义来看,优先使用offer()和poll(),因为不抛异常的方法比较容易处理。
2. ConcurrentLinkedQueue是什么?
ConcurrentLinkedQueue是基于链接节点实现的无界的线程安全的先进先出的非阻塞的队列。其链接节点的结构为:
private static class Node<E> {
volatile E item;
volatile Node<E> next;
}
每一个链接节点(Node)包含节点元素(item)和指向下一个节点的引用(next)。
为了方便对ConcurrentLinkedQueue的读写操作,又定义了两个变量:头结点head;尾节点tail
// 头结点,所有后继节点都可以从head开始,使用succ()方法访问到
/**
* A node from which the first live (non-deleted) node (if any)
* can be reached in O(1) time.
* Invariants:
* - all live nodes are reachable from head via succ()
* - head != null
* - (tmp = head).next != tmp || tmp != head
* Non-invariants:
* - head.item may or may not be null.
* - it is permitted for tail to lag behind head, that is, for tail
* to not be reachable from head!
*/
private transient volatile Node<E> head;
// 尾节点,
/**
* A node from which the last node on list (that is, the unique
* node with node.next == null) can be reached in O(1) time.
* Invariants:
* - the last node is always reachable from tail via succ()
* - tail != null
* Non-invariants:
* - tail.item may or may not be null.
* - it is permitted for tail to lag behind head, that is, for tail
* to not be reachable from head!
* - tail.next may or may not be self-pointing to tail.
*/
private transient volatile Node<E> tail;
头尾节点的注释中都提到了succ()方法,succ()方法是什么呢?
// succ()方法是返回节点p的后继节点。如果节点p的后继节点指向自己,则返回头结点。这种情况是如何发生的?(节点p已经不在链表中了?)
final Node<E> succ(Node<E> p) {
Node<E> next = p.next;
return (p == next) ? head : next;
}
succ()方法主要用途有什么?
(1). size(), 求队列大小
// 返回队列中元素个数,可以看到元素个数是int类型, 如果元素个数超过了Integer.MAX_VALUE的话,也只能返回Integer.MAX_VALUE
// 另外,这个方法返回的值是不精确的。
public int size() {
int count = 0;
// 从第一个节点开始遍历,如果节点不为null,统计节点个数,然后使用succ()方法获取下一个节点
for (Node<E> p = first(); p != null; p = succ(p))
if (p.item != null)
// Collection.size() spec says to max out
if (++count == Integer.MAX_VALUE)
break;
return count;
}
(2). contains()方法中succ()的用法与求队列大小类似
public boolean contains(Object o) {
if (o == null) return false;
for (Node<E> p = first(); p != null; p = succ(p)) {
E item = p.item;
if (item != null && o.equals(item))
return true;
}
return false;
}
3. ConcurrentLinkedQueue的构造函数为:
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
从构造函数看,ConcurrentLinkedQueue的头结点是包含null元素的一个节点,并且初始条件下head节点指向tail节点。
接下来看下head和tail是如何在offer()和poll()方法中怎么使用的。
// 插入元素到队尾
public boolean offer(E e) {
// 检查元素e是否为null,如果为null,抛出NullPointerException
checkNotNull(e);
// 创建新节点newNode
final java.util.concurrent.ConcurrentLinkedQueue.Node<E>
newNode = new java.util.concurrent.ConcurrentLinkedQueue.Node<E>(e);
// 首先赋值tail给t (t = tail),赋值t给p (p = t)
// 然后执行死循环for(;;)
for (java.util.concurrent.ConcurrentLinkedQueue.Node<E> t = tail, p = t;;) {
// 将p的next赋值给q, p.next -> q
java.util.concurrent.ConcurrentLinkedQueue.Node<E> q = p.next;
// 如果q为null,表示p是尾节点
if (q == null) {
// p是尾节点,将新节点newNode赋值给p的next,p.next -> e(newNode)
// 这个赋值过程是使用CAS来实现的,CAS比较并交换,意思就是如果newNode != null,则交换他们
if (p.casNext(null, newNode)) {
// 如果p != t,即p != t = tail,表示t(= tail)不是尾节点
if (p != t)
// 将t置为尾节点,该操作允许失败,因此t(= tail)并不总是尾节点
// 因此需要执行for(;;),先找到尾节点
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)
// 如果p == q, 说明尾节点tail已经不在链表中了,
// 这种情况下,跳转到head,因为从head开始所有的节点都可达
p = (t != (t = tail)) ? t : head;
else
// 如果p == q且q == null,p指向q,即p跳转到下一个元素
p = (p != t && t != (t = tail)) ? t : q;
}
}
public E poll() {
// 跳出for(;;)循环的标志位
restartFromHead:
for (;;) {
// 首先赋值head给h (h = head),赋值h给p (p = h),并定义变量q
// 然后执行死循环for(;;)
for (java.util.concurrent.ConcurrentLinkedQueue.Node<E> h = head, p = h, q;;) {
// 获取p的元素值,即头节点的元素值
E item = p.item;
// 如果元素值不为null,并将p的元素置null
// casItem(item, null)意思是如果item != null,则交换两者
// 交换之后,item就从队列中被移除了
if (item != null && p.casItem(item, null)) {
if (p != h) // 如果p不是指向h (head),更新head的值
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;
}
}
}
4. 生产者消费者使用ConcurrentLinkedQueue
import java.util.concurrent.ConcurrentLinkedQueue; public class ProducerAndConsumer {
private static ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); static class Producer extends Thread {
String name; public Producer(String name) {
this.name = name;
} public void run() {
for (int i = 0; i < 10; i++) {
queue.offer(i);
System.out.println(name + " : " + i);
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} static class Consumer extends Thread {
String name; public Consumer(String name) {
this.name = name;
} public void run() {
for (;;) {
Object item = queue.poll();
System.out.println(name + " : " + item);
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public static void main(String[] args) {
new Producer("p1").start();
new Producer("p2").start();
new Consumer("c1").start();
// new Consumer("c2").start();
}
}
ConcurrentLinkedQueue代码解析的更多相关文章
- VBA常用代码解析
031 删除工作表中的空行 如果需要删除工作表中所有的空行,可以使用下面的代码. Sub DelBlankRow() DimrRow As Long DimLRow As Long Dimi As L ...
- [nRF51822] 12、基础实验代码解析大全 · 实验19 - PWM
一.PWM概述: PWM(Pulse Width Modulation):脉冲宽度调制技术,通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形. PWM 的几个基本概念: 1) 占空比:占空比是指 ...
- [nRF51822] 11、基础实验代码解析大全 · 实验16 - 内部FLASH读写
一.实验内容: 通过串口发送单个字符到NRF51822,NRF51822 接收到字符后将其写入到FLASH 的最后一页,之后将其读出并通过串口打印出数据. 二.nRF51822芯片内部flash知识 ...
- [nRF51822] 10、基础实验代码解析大全 · 实验15 - RTC
一.实验内容: 配置NRF51822 的RTC0 的TICK 频率为8Hz,COMPARE0 匹配事件触发周期为3 秒,并使能了TICK 和COMPARE0 中断. TICK 中断中驱动指示灯D1 翻 ...
- [nRF51822] 9、基础实验代码解析大全 · 实验12 - ADC
一.本实验ADC 配置 分辨率:10 位. 输入通道:5,即使用输入通道AIN5 检测电位器的电压. ADC 基准电压:1.2V. 二.NRF51822 ADC 管脚分布 NRF51822 的ADC ...
- java集合框架之java HashMap代码解析
java集合框架之java HashMap代码解析 文章Java集合框架综述后,具体集合类的代码,首先以既熟悉又陌生的HashMap开始. 源自http://www.codeceo.com/arti ...
- Kakfa揭秘 Day8 DirectKafkaStream代码解析
Kakfa揭秘 Day8 DirectKafkaStream代码解析 今天让我们进入SparkStreaming,看一下其中重要的Kafka模块DirectStream的具体实现. 构造Stream ...
- linux内存管理--slab及其代码解析
Linux内核使用了源自于 Solaris 的一种方法,但是这种方法在嵌入式系统中已经使用了很长时间了,它是将内存作为对象按照大小进行分配,被称为slab高速缓存. 内存管理的目标是提供一种方法,为实 ...
- MYSQL常见出错mysql_errno()代码解析
如题,今天遇到怎么一个问题, 在理论上代码是不会有问题的,但是还是报了如上的错误,把sql打印出來放到DB中却可以正常执行.真是郁闷,在百度里面 渡 了很久没有相关的解释,到时找到几个没有人回复的 & ...
随机推荐
- python 全栈开发,Day75(Django与Ajax,文件上传,ajax发送json数据,基于Ajax的文件上传,SweetAlert插件)
昨日内容回顾 基于对象的跨表查询 正向查询:关联属性在A表中,所以A对象找关联B表数据,正向查询 反向查询:关联属性在A表中,所以B对象找A对象,反向查询 一对多: 按字段:xx book ----- ...
- Mac配置Node.js环境
打开终端输入命令:(安装brew) ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/mast ...
- 《Kafka技术内幕》学习笔记
第一章 Kafka入门 1.1 Kafka流式数据平台 Kafka作为流式数据平台的特点: 消息系统:两种消息模型:队列和发布订阅. 队列模型:将处理工作平均分给消费组中的消费者成员. 发布订阅模型: ...
- 各种浏览器下的页面元素xpath获取方法
参考链接: http://blog.sina.com.cn/s/blog_654c6ec70100v1i2.html
- 洛谷 p1434 滑雪【记忆化搜索】
<题目链接> Michael喜欢滑雪.这并不奇怪,因为滑雪的确很刺激.可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你.Michael想知道 ...
- iOS 11开发教程(二十)iOS11应用视图美化按钮之设置按钮的状态
iOS 11开发教程(二十)iOS11应用视图美化按钮之设置按钮的状态 在示例2-2中,设置按钮的标题和颜色时,需要对按钮的状态进行设置,表示按钮在某一状态下的标题和标题颜色是什么样子.例如,UICo ...
- 一个新的Android Studio 2.3.3可以在稳定的频道中使用。A new Android Studio 2.3.3 is available in the stable channel.
作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:313134555@qq.com E-mail: 313134555 @qq.com 一个新的Android Studio 2.3 ...
- 洛谷.1919.[模板]A*B Problem升级版(FFT)
题目链接:洛谷.BZOJ2179 //将乘数拆成 a0*10^n + a1*10^(n-1) + ... + a_n-1的形式 //可以发现多项式乘法就模拟了竖式乘法 所以用FFT即可 注意处理进位 ...
- Python3练习题系列(10)——项目骨架构建
目标: 如何创建<项目“骨架”目录> 包含:项目文件布局.自动化测试代码,模组,以及安装脚本. 由于编写一个Python文件可以作为一个模块,一个带__init__.py的目录算一个包. ...
- struts2动态跳转action,修改和添加共用一个页面
<s:form action="role_%{ id == null ? 'add' : 'edit' }"> <s:hidden name="id&q ...