深入理解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关键字才得以 ...
随机推荐
- gulp 自动化构建网站(版本号静态资源)
前言 新的一年啦,感觉总是静不下心,用了一天的时间吧,算是对gulp 这个前端打包工具入门了,自己也写了demo,表示对这个稍微理解了吧, 当然还是有差距的啦,下面讲讲怎么用吧: 如何用 当然先去官网 ...
- python和CSV
lines=[]; 表头数据 lines.append(list(retlist[0].keys())) 明细数据 for city in city_list: { lines.append(list ...
- PHP循环while do while循环
<?php #从1打印到10,除了5 $i=1; while ($i<10) { if ($i==5) { $i++; continue; } echo $i++."<br ...
- NOIP2017提高A组模拟10.6】Biology
题目 trie 暴力就是对于每个询问的T个字符串 第i个和第i+1个直接个从后暴力枚举每位是否相同, 但这个方法TLE 我们考虑是否可以用更快的方法来求出两个字符串的最长公共后缀. 我们把所有的字符串 ...
- element-ui 弹出添加拖拽功能
1.新建dialog.js文件2.在main.js 中引入dialog.js import ‘./utils/dialog.js’3. 使用:<el-dialog v-dialogDrag&g ...
- JS实现深拷贝的几种方法
引 如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力. 此篇文章中也会简单阐述到栈 ...
- sh_12_字典的遍历
sh_12_字典的遍历 xiaoming_dict = {"name": "小明", ", "} # 迭代遍历字典 # 变量k是每一次循环中 ...
- Trie树(字典树)整理
字典树 (Trie) 用于存储字符串.树的每条边恰好表示一个字符,每个节点代表从根到该节点的路径所对应的字符串. 简介与操作实现可见蓝书P82~83. Trie字典树很好地利用了前缀,节省了很多空间. ...
- php的 strval函数
官方的解释 PHP strval() 函数 PHP 可用的函数 strval() 函数用于获取变量的字符串值. PHP 版本要求: PHP 4, PHP 5, PHP 7 语法 string strv ...
- idea 编译内存溢出
idea.max.intellisense.filesize=9999 idea.max.content.load.filesize=99999 idea.cycle.buffer.size=disa ...