深入理解java:2.3.4. 并发编程concurrent包 之容器ConcurrentLinkedQueue(非阻塞的并发队列---循环CAS)
1. 引言
在并发编程中我们有时候需要使用线程安全的队列。
如果我们要实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,另一种是使用非阻塞算法。
使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现,
而非阻塞的实现方式则可以使用循环CAS的方式来实现,本文让我们一起来研究下如何使用非阻塞的方式来实现线程安全队列ConcurrentLinkedQueue的。
2. ConcurrentLinkedQueue的介绍
ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,
当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。
入队列就是将入队节点添加到队列的尾部。
tail.casNext(null, n) CAS算法,往tail的Next 设入队节点n。
tail.casTail(tail, n) CAS算法,把入队节点n 标记为tail 。
01 |
public boolean offer(E e) { |
02 |
03 |
if (e == null) |
04 |
05 |
throw new NullPointerException(); |
06 |
07 |
Node</e><e> n = new Node</e><e>(e); |
08 |
09 |
for (;;) { |
10 |
11 |
Node</e><e> t = tail; |
12 |
13 |
if (t.casNext(null, n) && casTail(t, n)) { |
14 |
15 |
return true; |
16 |
17 |
} |
18 |
19 |
} |
20 |
21 |
} |
这个方法没有任何锁操作。线程安全完全由CAS操作和队列的算法来保证。整个算法的核心是for循环,这个循环没有出口,直到尝试成功,这也符合CAS操作的流程。
ConcurrentLinkedQueue使用时,应当注意的地方:
查看ConcurrentLinkedQueue的API ,
.size() 是要遍历一遍集合的,性能慢,所以尽量要避免用size而改用 isEmpty()
ConcurrentLinkedQueue并发队列的一个demo:
先建立一个测试pojo,Log.java两个字段,加上get、set方法
private Date date;
private String value;
然后建立队列类,如下:
/**
* 用于记录日志的队列,ConcurrentLinkedQueue <br/>
* 此队列按照 FIFO(先进先出)原则对元素进行排序,详见J2SE_API或JDK
* @author RSun
* 2012-2-22下午05:05:19
*/
public class SystemLogQueue {
private static Queue<Log> log_Queue;
static{
if (null == log_Queue) {
log_Queue = new ConcurrentLinkedQueue<Log>(); //基于链接节点的无界线程安全队列
}
}
/** 初始化创建队列 **/
public static void init() {
if (null == log_Queue) {
log_Queue = new ConcurrentLinkedQueue<Log>(); //基于链接节点的无界线程安全队列
}
}
/**
* 添加到队列方法,将指定元素插入此队列的尾部。
* @param log Log对象
* @return 成功返回true,否则抛出 IllegalStateException
*/
public static boolean add(Log log) {
return (log_Queue.add(log));//由于是无界队列(21亿个元素),基本上可以保证一直添加
}
/** 获取并移除此队列的头 ,如果此队列为空,则返回 null */
public static Log getPoll() {
return (log_Queue.poll());
}
/** 获取但不移除此队列的头;如果此队列为空,则返回 null **/
public static Log getPeek() {
return (log_Queue.peek());
}
/** 判断此队列是否有元素 ,没有返回true **/
public static boolean isEmpty() {
return (log_Queue.isEmpty());
}
/** 获取size,速度比较慢 **/
public static int getQueueSize() {
return (log_Queue.size());
}
public static void main(String[] args) {
System.out.println("队列是否有元素:" + !isEmpty());
Log log = new Log();
log_Queue.add(log);
System.out.println("队列是否有元素:" + !isEmpty());
Log log2 = new Log();
log2.setDate(new Date());
log2.setValue("哈哈哈");
log_Queue.add(log2);
System.out.println("队列元素个数:" + getQueueSize());
Log l = getPeek();
System.out.println("\n获取队列数据:" + l.getValue() + "---" + l.getDate());
System.out.println("队列元素个数:" + getQueueSize());
for (int i = 0; i < 2; i++) {
Log l2 = getPoll();
if(l2 != null){
System.out.println("\n获取队列数据并删除:" + l2.getValue() + "---" + l2.getDate());
}
System.out.println("队列元素个数:" + getQueueSize());
}
结果:
// 队列是否有元素:false
// 队列是否有元素:true
// 队列元素个数:2
// 获取队列数据:null---null
// 队列元素个数:2
// 获取队列数据并删除:null---null
// 队列元素个数:1
// 获取队列数据并删除:哈哈哈---Thu Nov 15 13:58:02 CST 2012
// 队列元素个数:0
}
}
深入理解java:2.3.4. 并发编程concurrent包 之容器ConcurrentLinkedQueue(非阻塞的并发队列---循环CAS)的更多相关文章
- 深入理解java:2.3.5. 并发编程concurrent包 之容器BlockingQueue(阻塞队列)
1. 什么是阻塞队列? 阻塞队列(BlockingQueue)是一个支持两个附加操作的队列. 这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空. 当队列满时,存储元素的线程会等待队列 ...
- 深入理解java:2.3.3. 并发编程concurrent包 之容器ConcurrentHashMap
线程不安全的HashMap 因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap. 效率低下的HashTable容器 H ...
- Python并发编程-concurrent包
Python并发编程-concurrent包 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.concurrent.futures包概述 3.2版本引入的模块. 异步并行任务编程 ...
- 深入理解java:2.3.2. 并发编程concurrent包 之重入锁/读写锁/条件锁
重入锁 Java中的重入锁(即ReentrantLock) 与JVM内置锁(即synchronized)一样,是一种排它锁. ReentrantLock提供了多样化的同步,比如有时间限制的同步(定 ...
- java多线程 --ConcurrentLinkedQueue 非阻塞 线程安全队列
ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部:当我们获取一个元素时,它会返回队列头 ...
- 深入理解java:2.3.6. 并发编程concurrent包 之管理类---线程池
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁 ...
- 深入理解java:2.3.1. 并发编程concurrent包 之Atomic原子操作(循环CAS)
java中,可能有一些场景,操作非常简单,但是容易存在并发问题,比如i++, 此时,如果依赖锁机制,可能带来性能损耗等问题, 于是,如何更加简单的实现原子性操作,就成为java中需要面对的一个问题. ...
- 并发编程:协程TCP、非阻塞IO、多路复用、
一.线程池实现阻塞IO 二.非阻塞IO模型 三.多路复用,降低CPU占用 四.模拟异步IO 一.线程池实现阻塞IO 线程阻塞IO 客户端 import socket c = socket.socket ...
- 【Java并发编程】6、volatile关键字解析&内存模型&并发编程中三概念
volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...
随机推荐
- Android Studio 创建项目后“Cannot resolve symbol” 解决办法
报错位置显示 “Cannot resolve symbol XXX”, 解决方法如下: 点击菜单中的 “File” -> “Invalidate Caches / Restart”,然后点击对话 ...
- 2-SAT (two-statisfiability) 算法 学习笔记
$2-SAT$问题指的是对于若干限制求出一组可行解的问题. 考虑对于$n$个值域为${0,1}$的变量$x_1 , x_2 ,...,x_n$ 满足若干限制: 若 $x_i = p$ 则 $x_j = ...
- Makefile样例
Makefile1 src = $(wildcard ./*cpp) obj = $(patsubst %.cpp, %.o,$(src)) target = test $(target) : $(o ...
- happens-before规则和as-if-serial语义
JSR-133使用happens-before的概念来阐述操作之间的内存可见性.在JMM中,如果一个操作执行的结果需要对另一个操作可见, 那么这2个操作之间必须要存在happens-before关系. ...
- mysql 日期转换sql函数
mysql提供了两个函数: from_unixtime(time_stamp) -> 将时间戳转换为日期 unix_timestamp(date) -> 将 ...
- 数据重塑图解—Pivot, Pivot-Table, Stack and Unstack
Pivot pivot函数用于创建一个新的派生表,该函数有三个参数:index, columns和values.你需要在原始表中指定这三个参数所对定的列名,接下来pivot函数会创建一个新的表格,其中 ...
- STS热部署方法(springboot)
sts热部署,即是在项目中修改代码不用重新启动服务,提高效率. 方法如下: 1.在pom文件中引入 devtools 依赖: <dependency> <groupId> ...
- springBoot 整合 mybatis 项目实战
二.springBoot 整合 mybatis 项目实战 前言 上一篇文章开始了我们的springboot序篇,我们配置了mysql数据库,但是我们sql语句直接写在controller中并且使用 ...
- 解析XML的几种方式:DOM、SAX、PULL
DOM解析 解析器读入整个文档,然后构建一个主流内存的树结构,然后代码就可以使用dom接口来操作这个树结构. 优点: 整个文档树在内存中,便于操作:支持删除.修改.重新排列等多种功能. 通过树形结构存 ...
- WebSocket 结合 Nginx 实现域名及 WSS 协议访问-Nginx配置
特别提示:本人博客部分有参考网络其他博客,但均是本人亲手编写过并验证通过.如发现博客有错误,请及时提出以免误导其他人,谢谢!欢迎转载,但记得标明文章出处:http://www.cnblogs.com/ ...