9.并发包非阻塞队列ConcurrentLinkedQueue
jdk1.7.0_79
队列是一种非常常用的数据结构,一进一出,先进先出。
在Java并发包中提供了两种类型的队列,非阻塞队列与阻塞队列,当然它们都是线程安全的,无需担心在多线程并发环境所带来的不可预知的问题。为什么会有非阻塞和阻塞之分呢?这里的非阻塞与阻塞在于有界与否,也就是在初始化时有没有给它一个默认的容量大小,对于阻塞有界队列来讲,如果队列满了的话,则任何线程都会阻塞不能进行入队操作,反之队列为空的话,则任何线程都不能进行出队操作。而对于非阻塞无界队列来讲则不会出现队列满或者队列空的情况。它们俩都保证线程的安全性,即不能有一个以上的线程同时对队列进行入队或者出队操作。
非阻塞队列:ConcurrentLinkedQueue
阻塞队列:ArrayBlockingQueue、LinkedBlockingQueue、……
本文介绍非阻塞队列——ConcurentLinkedQueue。
首先查看ConcurrentLinkedQueue默认构造函数,观察它在初始化时做了什么操作。
//ConcurrentLinkedQueue
public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}
可以看到ConcurrentLinkedQueue在其内部有一个头节点和尾节点,在初始化的时候指向一个节点。
对于入队(插入)操作一共提供了这么2个方法(实际上是一个):
|
|
|
|
|
入队(插入) |
add(e)(其内部调用offer方法,) |
offer(e)(插入到队列尾部,当队列无界将永远返回true) |
//ConcurrentLinkedQueue#offer
public boolean offer(E e) {
checkNotNull(e); //入队元素是否为空,不允许Null值入队
final Node<E> newNode = new Node<E>(e); //将入队元素构造为Node节点
/*tail指向的是队列尾节点,但有时tail.next才是真正指向的尾节点*/
for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) { //此时p指向的就是队列真正的尾节点
if(p.casNext(null, newNode)) { //cas算法,p.next = newNode
if (p != tail) //将tail指向队列尾节点
casTail(t, newNode);
return true;
}
}
else if (p == q)
p = (t != (t = tail)) ? t : head;
else
p = (p != t && t != (t = tail)) t : q;
}
}
offer入队过程如下图所示:
① 队列中没有元素,第一次入队操作:
进入循环体:
t = tail;
p = tail;
q = p.next = null;

判断尾节点的引用p是否指向的是尾节点(if(q == null))->是:
CAS算法将入队节点设置成尾节点的next节点(p.casNext(null, newNode))
判断tail尾节点指针的引用p是否大于等于1个next节点(if (p != t))->否
返回true

② 队列中有元素,进行入队操作:
1) 第一次循环:
t = tail;
p = tail;
q = p.next = Node1;

判断tail尾节点指针的引用p是否指向的是尾节点(if(q == null))->否
判断tail尾节点指针的引用p是否指向的是尾节点(else if (p == q))->否
将tail尾节点指针的引用p向后移动(p = (p != t && t != (t = tail)) ? t : q;)->p = Node1

2) 第二次循环:
t = tail;
p = Node1;
q = p.next = null;

判断tail尾节点指针的引用p是否指向真正的尾节点(if(q == null))->是:
CAS算法将入队节点设置成尾节点的next节点(p.casNext(null, newNode))
判断tail尾节点指针的引用p是否大于等于1个next节点(if (p != t))->是:
更新tail节点(casTail(t, nextNode))
返回true

入队的操作都是由CAS算法完成,显然是为了保证其安全性。整个入队过程首先要定位出尾节点,其次使用CAS算法将入队节点设置成尾节点的next节点。整个入队过程首先要定位队列的尾节点,如果将tail节点一直指向尾节点岂不是更好吗?每次即tail->next = newNode;tail = newNode;这样在单线程环境来确实没问题,但是,在多线程并发环境下就不得不要考虑线程安全,每次更新tail节点意味着每次都要使用CAS更新tail节点,这样入队效率必然降低,所以ConcurrentLinkedQueue的tail节点并不总是指向队列尾节点的原因就是减少更新tail节点的次数,提高入队效率。
对于出队(删除)操作一共提供了这么1个方法:

//ConcurrentLinkecQueue#poll
public E poll() {
restartFromHead:
for (;;) {
for (Node<E> h = head, p = h, q;;) {
E item = p.item;
if (item != null && p.casItem(item, null)) {
if (p != h)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
else if ((q = p.next) == null) {
13 updateHead(h, p);
return null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
以上面队列中有两个元素为例:(注意,初始时,head指向的是空节点)

出队(删除):
1) 第一次循环:
h = head;
p = head;
q = null;
item = p.item = null;

判断head节点指针的引用是否不是空节点(if (item != null))->否,即是空节点
判断(暂略)
判断(暂略)
将head节点指针的引用p向后移动(p = q)
2) 第二次循环:
h = head;
p = q = Node1;
q = Node1;
item = p.item = Node1.item;

判断head节点指针的引用p是否不是空节点(if (item != null))->是,即不是空节点:
判断head节点指针与p是否指向同一节点(if (p != h))->否:
更新头节点(updateHead(h, ((q = p.next) != null) ? q : p))
返回item

实际上继续出队会发现,出队和入队类似,不会每次出队都会更新head节点,原理也和tail一样。
对于ConcurrentLinkedQueue#size方法将会遍历整个队列,可想它的效率并不高,如果一定需要调用它的size方法,特别是for循环时,我建议一下写法:
for (int i = 0, int size = concurrentLinkedQueue.size(); i < size;i++)
因为这能保证不用每次循环都调用一次size方法遍历一遍队列。
9.并发包非阻塞队列ConcurrentLinkedQueue的更多相关文章
- (原创)JAVA阻塞队列LinkedBlockingQueue 以及非阻塞队列ConcurrentLinkedQueue 的区别
阻塞队列:线程安全 按 FIFO(先进先出)排序元素.队列的头部 是在队列中时间最长的元素.队列的尾部 是在队列中时间最短的元素.新元素插入到队列的尾部,并且队列检索操作会获得位于队列头部的元素.链接 ...
- Java并发容器之非阻塞队列ConcurrentLinkedQueue
参考资料:http://blog.csdn.net/chenchaofuck1/article/details/51660521 实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,阻塞队列就是 ...
- 多线程高并发编程(11) -- 非阻塞队列ConcurrentLinkedQueue源码分析
一.背景 要实现对队列的安全访问,有两种方式:阻塞算法和非阻塞算法.阻塞算法的实现是使用一把锁(出队和入队同一把锁ArrayBlockingQueue)和两把锁(出队和入队各一把锁LinkedBloc ...
- 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理
· 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...
- java阻塞队列与非阻塞队列
在并发编程中,有时候需要使用线程安全的队列.如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法. //使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入 ...
- Michael-Scott非阻塞队列(lock-free)算法的C实现
Michael-Scott非阻塞队列算法,即MS-queue算法,是1 9 9 6 年由Maged . M .Michael and M. L. Scott提出的,是最为经典的并发FIFO队列上的算法 ...
- 生产者-消费者 用非阻塞队列、Object.wait()、Object.notify()实现
非阻塞队列,需要考虑到: 1.并发中的同步 2.线程间通信 public class Quene_Pro_Con { //定义队列大小 private static int size = 10; // ...
- Java中的阻塞队列-ConcurrentLinkedQueue
http://ifeve.com/concurrentlinkedqueue/ 1. 引言 在并发编程中我们有时候需要使用线程安全的队列.如果我们要实现一个线程安全的队列有两种实现方式一种是使用 ...
- Java--concurrent并发包下阻塞队列介绍
JDK提供了7中阻塞队列,这里介绍其中3中,剩余的以此类推原理相同. 1.ArrayBlockingQueue package com.seeyon.queue; import java.util.c ...
随机推荐
- html、css、js实现简易计算器
学习HTML,CSS,JS一个月后,想着能自己是否能写出一个简单的东西,故编写了简易的计算器,之前也写过一个坦克大战,坦克大战的有些基本功能没有实现, 故也没有记录下来,想来,对这行初来咋到的,还是需 ...
- Android Weekly Notes Issue #254
Android Weekly Issue #254 April 23rd, 2017 Android Weekly Issue #254 本期内容包括: 如何用Kotlin写一个Gradle Plug ...
- jDialects:一个从Hibernate抽取的支持70多种数据库方言的原生SQL分页工具
jDialects(https://git.oschina.net/drinkjava2/jdialects) 是一个收集了大多数已知数据库方言的Java小项目,通常可用来创建分页SQL和建表DDL语 ...
- 使用window.btoa和window.atob来进行Base64编码和解码
方法描述 WindowBase64.atob() 函数用来解码一个已经被base-64编码过的数据. WindowBase64.btoa() 函数 将ascii字符串或二进制数据转换成一个base ...
- 直方图均衡化CImg实现
这篇博客是关于试用CImg库来实现灰度图和彩色图的直方图均衡化操作.感觉效果还不错,除了彩色图在均衡化时会有一定的色彩失真. C++代码实现: // // hEqualization.hpp // 直 ...
- 2017Unity开发者大会备受关注的原因有哪些?
Unite大会是由Unity举办的全球开发者大会,至今已有10年的历史.从最开始Unity开发者大会仅500人,到现在Unity大会已经增长到5000人,10倍的参与人数,Unity开发者大会仅仅用了 ...
- javascript ES3小测试
一.温故知新 做做题,总是能有温故知新的体验.这套题是2010年的了,比较老了, http://perfectionkills.com/ 还有一套http://perfectionkills.com ...
- 设计模式--MVC(C++版)
MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式.这种模式用于应用程序的分层开发. Model(模型)-是应用程序中用于处理应用程序数据逻辑的部分.通常模型对象 ...
- iOS 使用 UIMenuController 且不隐藏键盘的方法
iOS 使用 UIMenuController 且不隐藏键盘的方法 在键盘显示的时候使用 UIMenuController 弹出菜单,保持键盘显示且可输入的状态. 实现方法有 修改响应链(推荐) 遵循 ...
- Linux实战教学笔记11:linux定时任务
第十一节 linux定时任务 标签(空格分隔): Linux实战教学笔记 ---更多资料点我查看 1.1 定时任务Crond介绍 Crond是linux系统中用来定期执行命令/脚本或指定程序任务的一种 ...