Java并发包探秘 (一) ConcurrentLinkedQueue
本文是Java并发包探秘的第一篇,旨在介绍一下Java并发容器中用的一些思路和技巧,帮助大家更好的理解Java并发容器,让我们更好的使用并发容器打造更高效的程序。本人能力有限,错误难免。希望及时指出。
Java并发包中有很多精心设计的高并发容器。有ConcurrentHashMap、ConcurrentSkipListMap
、ConcurrentLinkedQueue等。ConcurrentLinkedQueue就是其中设计最为优雅的高并发容器。它被设计成了无锁的、无界的、非阻塞式的单向链表结构。现在就让我们来一步一步揭开他们神秘的面纱。
正文开始:
一说到链表结构,我们首先就会想到的就是组成链表结构的原件,那就是节点。或者有的人称之为元素。ConcurrentLinkedQueue(为了叙述方便后面用CLQ指代)中称之为Node.
我们先来看看CLQ中Node的结构:
- private static class Node<E> {
- private volatile E item;
- private volatile Node<E> next;
- private static final
- AtomicReferenceFieldUpdater<Node, Node>
- nextUpdater =
- AtomicReferenceFieldUpdater.newUpdater
- (Node.class, Node.class, "next");
- private static final
- AtomicReferenceFieldUpdater<Node, Object>
- itemUpdater =
- AtomicReferenceFieldUpdater.newUpdater
- (Node.class, Object.class, "item");
- Node(E x) { item = x; }
- Node(E x, Node<E> n) { item = x; next = n; }
- E getItem() {
- return item;
- }
- boolean casItem(E cmp, E val) {
- return itemUpdater.compareAndSet(this, cmp, val);
- }
- void setItem(E val) {
- itemUpdater.set(this, val);
- }
- Node<E> getNext() {
- return next;
- }
- boolean casNext(Node<E> cmp, Node<E> val) {
- return nextUpdater.compareAndSet(this, cmp, val);
- }
- void setNext(Node<E> val) {
- nextUpdater.set(this, val);
- }
- }
- private static class Node<E> {
- private volatile E item;
- private volatile Node<E> next;
- private static final
- AtomicReferenceFieldUpdater<Node, Node>
- nextUpdater =
- AtomicReferenceFieldUpdater.newUpdater
- (Node.class, Node.class, "next");
- private static final
- AtomicReferenceFieldUpdater<Node, Object>
- itemUpdater =
- AtomicReferenceFieldUpdater.newUpdater
- (Node.class, Object.class, "item");
- Node(E x) { item = x; }
- Node(E x, Node<E> n) { item = x; next = n; }
- E getItem() {
- return item;
- }
- boolean casItem(E cmp, E val) {
- return itemUpdater.compareAndSet(this, cmp, val);
- }
- void setItem(E val) {
- itemUpdater.set(this, val);
- }
- Node<E> getNext() {
- return next;
- }
- boolean casNext(Node<E> cmp, Node<E> val) {
- return nextUpdater.compareAndSet(this, cmp, val);
- }
- void setNext(Node<E> val) {
- nextUpdater.set(this, val);
- }
- }
1.CLQ中的Node定义成了私有的静态类说明该节点描述只适用在CLQ中。
2.其中用到了AtomicReferenceFieldUpdater原子属性引用原子更新器。该类是抽象的,但该类的内部已经给出了包访问控制级别的一个实现AtomicReferenceFieldUpdaterImpl,原理是利用反射将一个被声明成
volatile 的属性通过Java native interface
(JNI)调用,使用cpu指令级的命令将一个变量进行更新,该操作是原子的。atomic
包中还有很多类似的更新器分别针对不同的类型进行原子级别的比较更新原子操作。这里用到了sun.misc.Unsafe 的
compareAndSwap操作(简称CAS)它有三种不同的本地命令分别针对Int、Long、Object进行原子更新操作。
3.我们可以看出CLQ中的Node结构是一个单向的链表结构,因为每个Node只有一个向后的next和一个item用来装内容。CLQ将通过casNext和casItem方法来原子更新Node链的结构。setNext 和setItem则是直接放入
我们再来看CLQ的链结构
- private static final
- AtomicReferenceFieldUpdater<ConcurrentLinkedQueue, Node>
- tailUpdater =
- AtomicReferenceFieldUpdater.newUpdater
- (ConcurrentLinkedQueue.class, Node.class, "tail");
- private static final
- AtomicReferenceFieldUpdater<ConcurrentLinkedQueue, Node>
- headUpdater =
- AtomicReferenceFieldUpdater.newUpdater
- (ConcurrentLinkedQueue.class, Node.class, "head");
- private boolean casTail(Node<E> cmp, Node<E> val) {
- return tailUpdater.compareAndSet(this, cmp, val);
- }
- private boolean casHead(Node<E> cmp, Node<E> val) {
- return headUpdater.compareAndSet(this, cmp, val);
- }
- /**
- * Pointer to header node, initialized to a dummy node. The first
- * actual node is at head.getNext().
- */
- private transient volatile Node<E> head = new Node<E>(null, null);
- /** Pointer to last node on list **/
- private transient volatile Node<E> tail = head;
- private static final
- AtomicReferenceFieldUpdater<ConcurrentLinkedQueue, Node>
- tailUpdater =
- AtomicReferenceFieldUpdater.newUpdater
- (ConcurrentLinkedQueue.class, Node.class, "tail");
- private static final
- AtomicReferenceFieldUpdater<ConcurrentLinkedQueue, Node>
- headUpdater =
- AtomicReferenceFieldUpdater.newUpdater
- (ConcurrentLinkedQueue.class, Node.class, "head");
- private boolean casTail(Node<E> cmp, Node<E> val) {
- return tailUpdater.compareAndSet(this, cmp, val);
- }
- private boolean casHead(Node<E> cmp, Node<E> val) {
- return headUpdater.compareAndSet(this, cmp, val);
- }
- /**
- * Pointer to header node, initialized to a dummy node. The first
- * actual node is at head.getNext().
- */
- private transient volatile Node<E> head = new Node<E>(null, null);
- /** Pointer to last node on list **/
- private transient volatile Node<E> tail = head;
1.实际上经过对Node的分析。CLQ中的头尾指针的更新原理其实也是一样的。都是通过cpu原子操作命令进行的更新。
2.这样我们就有了在高并发下原子更新的基础支持,但是除了原子更新的支持是不够的。原因很简单,这是因为当多个线程同时使用原子更新操作来更新一个链表结构的时候只有一个成功其它的都会失败。失败的操作如何再让它成功才是问题的关键。CLQ优雅的解决了这一问题。
我们再来看看CLQ的放入元素操作:
- public boolean offer(E e) {
- if (e == null) throw new NullPointerException();
- Node<E> n = new Node<E>(e, null);
- for (;;) { //1
- Node<E> t = tail; //2
- Node<E> s = t.getNext(); //3
- if (t == tail) { //4
- if (s == null) { //5
- if (t.casNext(s, n)) { //6
- casTail(t, n); //7
- return true; //8
- }
- } else {
- casTail(t, s); //9
- }
- }
- }
- }
- public boolean offer(E e) {
- if (e == null) throw new NullPointerException();
- Node<E> n = new Node<E>(e, null);
- for (;;) { //1
- Node<E> t = tail; //2
- Node<E> s = t.getNext(); //3
- if (t == tail) { //4
- if (s == null) { //5
- if (t.casNext(s, n)) { //6
- casTail(t, n); //7
- return true; //8
- }
- } else {
- casTail(t, s); //9
- }
- }
- }
- }
在有锁得情况下我们只要让获得锁得线程更新,其它线程等待即可解决并发更新的问题,但是在上述的单向链表结构中有更好的无锁解决方法。
1.代码1 啊! 死循环,对,就是利用反复轮询的重复一段逻辑操作。
2.代码2 代码3 先用两个临时变量指向CLQ的尾和尾的下一个节点。这样有什么好处?直接用tail和tail.getNext不行吗?我们说了。这是一个无锁得方法。可能有多个线程同时执行到代码3处,因为临时变量是每线程的而tail是公共的。这样成功执行到代码3的线程都有自己当时的临时CLQ队列结构引用。为后面的判断做好准备。
3.开始判断 代码4 证明 在代码2 和代码4之间没有被其它线程修改过,因为有可能已经被修改了。那么这时进入新的轮询。
4.代码5 在看代码5之前我们先要明确一个概念就是把一个Node放入一个CLQ队列有两步操作。第一步是tail的next指向新的节点。第二步是tail指向新的节点。代码5 先判断是不是有线程已经在完成加入一个节点的第一步,如果是就帮助它完成第二步,再次进入循环。如果没有线程已经完成第一步。那就自己来完成插入节点的第一步,当然就是调用casNext比较更新的原子操作。上文已经讲过。再来完成插入元素的第二步,以上逻辑由代码6、代码7完成。注意看代码8
恒为真? 为什么?自己调用casTail如果成功返回真毫无疑问。如果失败为什么也返回真?答案很简单,这是因为如果失败说明一定有其它线程进入了代码9 帮自己完成了插入一个节点的第二步操作。所以自己操作肯定是失败的。所以也返回真。
从上面的代码分析可以看出打造一个无锁得并发容器处处都要十分小心。这也是CLQ的高明之处。
我们再来看看删除一个元素的代码:
- public E poll() {
- for (;;) {
- Node<E> h = head; //1
- Node<E> t = tail; //2
- Node<E> first = h.getNext(); //3
- if (h == head) { //4
- if (h == t) { //5
- if (first == null) //6
- return null; //7
- else
- casTail(t, first); //8
- } else if (casHead(h, first)) { //9
- E item = first.getItem(); //10
- if (item != null) { //11
- first.setItem(null); //12
- return item; //13
- }
- // else skip over deleted item, continue loop,
- }
- }
- }
- }
- public E poll() {
- for (;;) {
- Node<E> h = head; //1
- Node<E> t = tail; //2
- Node<E> first = h.getNext(); //3
- if (h == head) { //4
- if (h == t) { //5
- if (first == null) //6
- return null; //7
- else
- casTail(t, first); //8
- } else if (casHead(h, first)) { //9
- E item = first.getItem(); //10
- if (item != null) { //11
- first.setItem(null); //12
- return item; //13
- }
- // else skip over deleted item, continue loop,
- }
- }
- }
- }
1.同样是轮询,当h!=head的时候继续循环。因为在代码1和代码4之间已经有其它线程删除了头元素。从而造成h != head.
2.代码5 是否是空的CLQ。
3.如果是空的CLQ判断头得下一节点是否是null.因为只有时空的才说明没有元素。否则有可能其它线程正在插入元素造成first!=null,这时就帮助其它线程完成为指针更新操作。再继续轮询。
4.如果是非空的CLQ用casHead来原子更新头节点。因为删除一个CLQ的元素是从头开始删除的。如果失败说明有其它线程在删除元素。继续轮询。
5.代码10 如果第一个元素的内容为空说明有线程已经执行到代码12了。所以又开始轮询。
6.只有成功执行到代码13才正真是由当前线程完成了删除一个元素操作。CLQ的peek()操作和poll操作只差代码12的操作,即一个删除元素,一个不删除元素。
在CLQ中迭代器的方法也很精妙:
- private E advance() {
- lastRet = nextNode;
- E x = nextItem;
- Node<E> p = (nextNode == null)? first() : nextNode.getNext();
- for (;;) {
- if (p == null) {
- nextNode = null;
- nextItem = null;
- return x;
- }
- E item = p.getItem();
- if (item != null) {
- nextNode = p;
- nextItem = item;
- return x;
- } else // skip over nulls
- p = p.getNext();
- }
- }
- private E advance() {
- lastRet = nextNode;
- E x = nextItem;
- Node<E> p = (nextNode == null)? first() : nextNode.getNext();
- for (;;) {
- if (p == null) {
- nextNode = null;
- nextItem = null;
- return x;
- }
- E item = p.getItem();
- if (item != null) {
- nextNode = p;
- nextItem = item;
- return x;
- } else // skip over nulls
- p = p.getNext();
- }
- }
由于CLQ单向链表的特殊性,元素的变化只可能头处删除,在尾处添加。所以使用CLQ的迭代器时元素可能比实际的要多。原因很简单,当你在迭代的时候元素可能已经删除,当然这是你迭代的线程是不可见的。而删除是可见的。
ConcurrentLinkeQueue的其它操作大同小异。都是在不断的轮询中步步判断其它线程的影响,一步一步推进自己的操作逻辑。从而最终完成操作的。
Java并发包探秘 (一) ConcurrentLinkedQueue的更多相关文章
- Java并发包——Blockingqueue,ConcurrentLinkedQueue,Executors
背景 通过做以下一个小的接口系统gate,了解一下mina和java并发包里的东西.A系统为javaweb项目,B为C语言项目,gate是本篇须要完毕的系统. 需求 1. A为集群系统,并发较高,会批 ...
- Java并发包源码学习系列:基于CAS非阻塞并发队列ConcurrentLinkedQueue源码解析
目录 非阻塞并发队列ConcurrentLinkedQueue概述 结构组成 基本不变式 head的不变式与可变式 tail的不变式与可变式 offer操作 源码解析 图解offer操作 JDK1.6 ...
- java并发包&线程池原理分析&锁的深度化
java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的, ...
- Java并发包——线程安全的Collection相关类
Java并发包——线程安全的Collection相关类 摘要:本文主要学习了Java并发包下线程安全的Collection相关的类. 部分内容来自以下博客: https://www.cnblogs.c ...
- HashMap、Hashtable、ConcurrentHashMap、ConcurrentSkipListMap对比及java并发包(java.util.concurrent)
一.基础普及 接口(interface) 类(class) 继承类 实现的接口 Array √ Collection √ Set √ Collection List √ Collection Map ...
- java并发包提供的三种常用并发队列实现
java并发包中提供了三个常用的并发队列实现,分别是:ConcurrentLinkedQueue.LinkedBlockingQueue和ArrayBlockingQueue. ConcurrentL ...
- Java并发包源码学习系列:挂起与唤醒线程LockSupport工具类
目录 LockSupport概述 park与unpark相关方法 中断演示 blocker的作用 测试无blocker 测试带blocker JDK提供的demo 总结 参考阅读 系列传送门: Jav ...
- Java并发包源码学习系列:阻塞队列实现之LinkedTransferQueue源码解析
目录 LinkedTransferQueue概述 TransferQueue 类图结构及重要字段 Node节点 前置:xfer方法的定义 队列操作三大类 插入元素put.add.offer 获取元素t ...
- Java并发包源码学习系列:线程池ThreadPoolExecutor源码解析
目录 ThreadPoolExecutor概述 线程池解决的优点 线程池处理流程 创建线程池 重要常量及字段 线程池的五种状态及转换 ThreadPoolExecutor构造参数及参数意义 Work类 ...
随机推荐
- TCP/IP图解学习总结(二)
注意:这里的第n层是依照OSI协议来的 I 网桥--2层交换机.数据链路层面上链接两个网络的设备.它可以识别数据链路层中的数据帧. II 路由器-3层交换机.网络层面上连接两个网络,并对分组报文 ...
- CSS布局篇——固宽、变宽、固宽+变宽
学了前端挺久了.近期写一个项目測试系统,布局时发现自己对变宽+固宽的布局还没有全然掌握,所以在这里总结一下,以后须要的时候回头看看. 1.最简单的当然是一列或多列固宽 比如两列固宽: <1> ...
- 2.5 Legacy APIs官网剖析(博主推荐)
不多说,直接上干货! 一切来源于官网 http://kafka.apache.org/documentation/ 2.5 Legacy APIs A more limited legacy prod ...
- JSTL之C标签学习
JSTL 核心标签库标签共有13个,功能上分为4类: 1.表达式控制标签:out.set.remove.catch 2.流程控制标签:if.choose.when.otherwise 3.循环标签:f ...
- Manning.EJB.3.in.Action.2nd.Edition
Manning.EJB.3.in.Action.2nd.Edition http://files.cnblogs.com/files/rojas/EJB_3_in_Action_2nd_Edition ...
- django-rest-framework框架 第四篇 认证Authentication
认证Authentication 什么是身份认证 身份验证是将传入请求与一组标识凭据(例如请求来自的用户或与其签名的令牌)关联的机制. 视图的最开始处运行身份验证 在权限和限制检查发生之前,以及在允许 ...
- Quartz学习总结(1)——Spring集成Quartz框架
一.Quartz简介 Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来创建简 ...
- ED/EP系列1《简单介绍》
电子存折(ED:ElectronicDeposit)一种为持卡人进行消费.取现等交易而设计的支持个人识别码(PIN)保护的金融IC卡应用. 它支持圈存.圈提.消费和取现等交易. 电子钱包(EP:Ele ...
- 手把手教你用NDK9编译ffmpeg2.4.2
编译环境: 32位 ubuntu12.10 android-ndk-r9c-linux-x86.tar.bz2 ffmpeg-2.4.2.tar.bz2 网上的教程都是以低版本号ffmpeg编译居多. ...
- BeautifulSoup的高级应用 之 contents children descendants string strings stripped_strings
继上一节.BeautifulSoup的高级应用 之 find findAll,这一节,主要解说BeautifulSoup有关的其它几个重要应用函数. 本篇中,所使用的html为: html_doc = ...