队列、链表之类的数据结构及其常用。Java中,ArrayList和Vector都是使用数组作为其内部实现。两者最大的不同在于:Vector是线程安全的,而ArrayList不是。此外LinkedList使用链表的数据结构实现了List,但并不是线程安全的,就像之前包装HashMap,这里我们可以使用Collections.synchronizedList()来包装任意List。此时,生成的List对象就是线程安全的。

public static List<String> l = Collections.synchronizedList(new LinkedList<String>());

高效读写的队列:深度剖析ConcurrentLinkedQueue

 JDK中,提供了一个ConcurrentLinkedQueue类来实现高并发的队列。这个队列使用链表作为其数据结构。这个类算是高并发环境中性能最好的队列。高性能是因为其内部复杂的实现。

链表内的节点:

    private static class Node<E> {
volatile E item;
volatile Node<E> next;
...

其实这是一个静态内部类,item是来表示元素的。next表示当前Node的下一个元素。 
对Node进行操作时,使用了CAS操作:

//表示设置当前Node的item值。第一个参数为期望值,第二个参数为设置目标值。当当前值等于期望值时(就是没有被其他人改过),就会将目标设置为val。
boolean casItem(E cmp, E val) {
return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
} //和上个方法类似,是用来设置next字段。
boolean casNext(Node<E> cmp, Node<E> val) {
return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
}

ConcurrentLinkedQueue内部有两个重要的字段:head和tail,分别表示链表的头部和尾部,当然都得是Node类型。对于head来说,它永远不会为null,并且通过head和succ()后继方法一定能完整地遍历整个链表。对于tail来说,它自然表示队列的末尾。 
以下是构造函数:

    public ConcurrentLinkedQueue() {
head = tail = new Node<E>(null);
}

但是ConcurrentLinkedQueue的内部实现非常复杂,它允许在运行时链表处于多个不同的状态。以tail为例,一般来说我们期望tail总是作为链表的末尾,但实际上tail的更新并不是及时的,可能会产生拖延现象。就是说,有可能指向的并不是真正的尾巴,真正的可能在后面一个或者多个。

下面就是向队列中添加元素的offer方法:

/**
* Inserts the specified element at the tail of this queue.
* 在队列后插入一个元素
* As the queue is unbounded, this method will never return {@code false}.
* 因为队列是无限大的,这个方法永远不会回false
*
* @return {@code true} (as specified by {@link Queue#offer})
* 返回true,
* @throws NullPointerException if the specified element is null
* 如果某元素为空则抛出空指针异常
*/
public boolean offer(E e) {
checkNotNull(e);//检查e是否为空
final Node<E> newNode = new Node<E>(e);//创建新节点 for (Node<E> t = tail, p = t;;) {
Node<E> q = p.next;
if (q == null) { //1
// p is last node. p是最后一个节点
if (p.casNext(null, newNode)) {
// Successful CAS is the linearization point
// for e to become an element of this queue,
// and for newNode to become "live".
if (p != t) // hop two nodes at a time
//每两次,更新一下tail
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next CAS竞争失败,再次尝试。
}
else if (p == q)//2
// We have fallen off list. If tail is unchanged, it
// will also be off-list, in which case we need to
// jump to head, from which all live nodes are always
// reachable. Else the new tail is a better bet.
//遇到哨兵节点,从head开始遍历。
//但如果tail被修改,则使用tail(因为可能被修改正确了)
p = (t != (t = tail)) ? t : head;
else //3
// Check for tail updates after two hops.取下一个节点或者最后一个节点
p = (p != t && t != (t = tail)) ? t : q;
}
}

这个方法没有任何锁操作。线程安全完全由CAS操作和队列的算法来保证。整个算法的核心是for循环,这个循环没有出口,知道尝试成功,这也符合CAS操作的流程。 
当第一次加入元素的时候,由于队列为空,因此p.next为null。程序进入1处,程序将p的next节点赋值为newNode,也就是将新的元素加入到队列中。此时p==t成立,也就是不会执行casTail()代码更新tail末尾。如果casNext()成功,程序直接返回,如果失败,则再进行一次循环尝试,直到成功。因此,增加一个元素后,tail并不会被更新。 
当程序尝试加入第二个元素时,由于此时t还在head的位置上,因此p.next指向实际的第一个元素,因此1处的q!=null,这表示q不是最后的节点。由于往队列中增加元素需要最后一个节点,因此,循环开始中查找最后一个节点。于是,程序进入3处,获得最后一个节点。此时,p实际上是指向链表中的第一个元素,它的next是null,故在第二个循环的时候,进入1处,p更新自己的next,让它指向新加入的节点。如果成功,由于此时p!=t成功,则会更新t所在位置,将t移到链表最后。 
2处处理了p==q的情况。这种情况是由于遇到了哨兵节点导致的。所谓哨兵节点,就是next指向自己的节点。这种节点在队列中的存在价值不大,主要表示要删除的节点或者空节点。当遇到哨兵节点的时候,由于无法通过next取得后续的节点,因此很可能直接返回head,期望通过从链表头部开始遍历,进一步查找到链表末尾。但一旦发生在执行过程中,tail被其他线程修改的情况,则进行一次“打赌”,使用新的tail作为链表末尾,这样就避免了重新查找tail的开销。

p = (p != t && t != (t = tail)) ? t : q;

这段代码中:“!=”并不是原子操作,它是可以被中断的。也就是说,在执行“!=”时,程序会先取得t的值,在执行t=tail,并取得新的t值。然后比较这两个值是否相等。在单线程时,t!=t显然是不成立的。但是在并发环境中,有可能在获得左边的t值后,右边的t值被其他线程修改。这样t!=t就可能成立。这就表示tail在中途被其他线程篡改。这时,我们就可以用新的tail作为链表末尾,也就是等式右边的t。如果tail没有被修改,则返回head,要求从头部开始,重新查找尾部。 

3.3.4深度剖析ConcurrentLinkedQueue的更多相关文章

  1. 《AngularJS深度剖析与最佳实践》简介

    由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...

  2. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  3. Objective-C类成员变量深度剖析

    目录 Non Fragile ivars 为什么Non Fragile ivars很关键 如何寻址类成员变量 真正的“如何寻址类成员变量” Non Fragile ivars布局调整 为什么Objec ...

  4. 大众点评开源分布式监控平台 CAT 深度剖析

    一.CAT介绍 CAT系统原型和理念来源于eBay的CAL的系统,CAT系统第一代设计者吴其敏在eBay工作长达十几年,对CAL系统有深刻的理解.CAT不仅增强了CAL系统核心模型,还添加了更丰富的报 ...

  5. 深度剖析WordPress主题结构(转)

    利用强大的技术,可以把基于wordpress的网站做成各种各样的形式,这除了要求wordpress主题开发人员精通html,PHP,JS,CSS等技术,还需要开发者掌握WordPress主题的框架. ...

  6. LCD深度剖析

    LCD 深度剖析 来源:http://blog.csdn.net/hardy_2009/article/details/6922900 http://blog.csdn.net/jaylondon/a ...

  7. WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇)

    原文:WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话 ...

  8. 大兴雷克萨斯深度剖析2013款LS460L_深圳大兴雷克萨斯_太平洋汽车网

    大兴雷克萨斯深度剖析2013款LS460L_深圳大兴雷克萨斯_太平洋汽车网 大兴雷克萨斯深度剖析2013款LS460L

  9. Java反射机制剖析(四)-深度剖析动态代理原理及总结

    动态代理类原理(示例代码参见java反射机制剖析(三)) a)  理解上面的动态代理示例流程 a)  理解上面的动态代理示例流程 b)  代理接口实现类源代码剖析 咱们一起来剖析一下代理实现类($Pr ...

随机推荐

  1. 螺旋填数:读入两个整数m,n,输出一个m行n列的矩阵,这个矩阵是1~m*n这些自然数按照右、下、左、上螺旋填入的结果。

    package Day8_06; /*读入两个整数m,n,输出一个m行n列的矩阵,这个矩阵是1~m*n这些自然数按照右.下.左.上螺旋填入的结果. * 例如读入数字4,5,则输出结果为: * 1 2 ...

  2. 利用nginx_push_stream_module实现服务器消息推送

    NGiNX_HTTP_Push_Module 是一个 Nginx 的扩展模块,它实现了 HTTP Push 和Comet server的功能.HTTP Push 被经常用在网页上主动推的技术,例如一些 ...

  3. 杂项:NoSQL

    ylbtech-杂项:NoSQL NoSQL,泛指非关系型的数据库.随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站 ...

  4. docker监控的一点想法

    目前项目内部署了docker,于是涉及到关于监控的事情,参考一些经典实例以及一些自己的想法,总结一下思路. 1.关于监控的内容 监控宿主机本身 监控宿主机本身还是比较简单的,同其他服务器监控类似,对c ...

  5. 配置PHP,Apache

    安装完windows 2003 server以后,还是个裸机,在安装limesurvey总是会有些问题,还好,问题都解决了,下面讲下配置的步骤: 第一步:先装上apache服务 apache服务启动以 ...

  6. Shell教程快速入门

    Shell即是一种命令语言,又是一种程序设计语言,使用者可以通过Shell访问操作系统的内核服务. Shell编程和java.python.C一样,只要一个能编写代码的文本编辑器和一个能解释执行的脚本 ...

  7. java爬虫简单实例

    爬虫的实质就是打开网页源代码进行匹配查找,然后获取查找到的结果./** 获取* 将正则规则进行对象的封装. * Pattern p = Pattern.compile("a*b") ...

  8. 跨境B2B电商

    主要处理问题:解决整个支付和流通环节,各国双方的供应商和销售商只关注下单支付后就可以拿到货物,中间环节由平台处理,支付和流通环节消费越少速度越快服务越好. 主体业务 1.合同处理. 2.货币支付,互换 ...

  9. 转:Numpy教程

    因为用到theano写函数的时候饱受数据结构困扰 于是上网找了一篇numpy教程(theano的数据类型是基于numpy的) 原文排版更好,阅读体验更佳: http://phddreamer.blog ...

  10. Spring实战之环境与profile

    1.配置profile bean Spring为环境相关的bean所提供的解决方案其实与构建时的方案没有太大的差别.当然,在这个过程中需要根据环境决定该创建哪个bean和不创建哪个bean.不过Spr ...