博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ConcurrentLinkedDeque源码解析
阅读量:4209 次
发布时间:2019-05-26

本文共 2879 字,大约阅读时间需要 9 分钟。

Java的并发包JUC大概包括五大部分,分别是原子操作类、锁机制、并发集合、并发框架和并发集合。在之前大概的阅读了前两者的实现过程。接下来准备读一读并发集合相关的实现。之前我们研究原子类的时候基本的实现是借助于valative和CAS来实现的,那么对于集合又如何让其在高并发的时候能够高效率的存储和获取,为什么这么说,难道JUC做的那么多并发集合就是为了解决这个问题?因为之前我们我们在锁的那块说过,如果加锁之后副本代码只能择一进行运行,所以显然并发集合肯定不是咋理解的那样,准确的说,并发集合是用来在并发量高的是。最基本是当一个线程修改或者删除了集合中的数据。其他线程可能获取的值变为脏数据。所以这块的并发集合就是要解决高并发情况下的存储和获取,以至于在高并发情况下能够尽量提升系统性能。

ConcurrentLinkedDeque是一个高并发链表,从上边的截图中可以看到。ConcurrentLinkedDeque的实现是采用Unsafe来实现的,其中包括了head和tail,而head和tail是长整形的类型。那么我们大概可以猜测出该类对链表的节点的操作是采用的Unsafe来操作的,而操作的参数就是head和tail的偏移量。如果采用Unsafe操作,那么我们需要看一下这个链表的节点定义,按照之前的学习,那么node应该是线程可见的。

在Node节点中定义中,我们看到链表的一般结构。但是我们发现在链表中我们依然看到了偏移量的定义,按照我们之前的想法,我觉得用一个偏移量就可以了,就是记录整个链表的的首和尾.

但是仔细想确实有问题,如果我们要操作中间一个节点的后置或者前置,那要去通过首和尾的偏移量去计算吗,如何中间某个节点足够大,那么根本没办法知道节点的开始和结束的位置,而且我们操作是基于Unsafe的,所以地址偏移量很重要。需要在每个节点记录一次。除此之外,node的每个节点提供对节点的CAS操作的方法。其实现的过程和我们在原子类中的CAS分析一样。这里不再赘述。

在剩余的内部类中,我们看到了迭代器。这里的迭代器就相当于ConcurrentLinkedDeque的专用迭代器。这里我们先不看迭代器,我们先看对链表操作的一般步骤就是增删改查的操作。

private void linkFirst(E e) {        checkNotNull(e);        final Node
newNode = new Node
(e); restartFromHead: for (;;) for (Node
h = head, p = h, q;;) { if ((q = p.prev) != null && (q = (p = q).prev) != null) // Check for head updates every other hop. // If p == q, we are sure to follow head instead. p = (h != (h = head)) ? h : q; else if (p.next == p) // PREV_TERMINATOR continue restartFromHead; else { // p is first node newNode.lazySetNext(p); // CAS piggyback if (p.casPrev(null, newNode)) { // Successful CAS is the linearization point // for e to become an element of this deque, // and for newNode to become "live". if (p != h) // hop two nodes at a time casHead(h, newNode); // Failure is OK. return; } // Lost CAS race to another thread; re-read prev } } }

在将元素添加到头部节点的时候,我们一般的操作是将头部的指针指向该节点,然后让该节点的next指向原来的头节点的next节点。大概的过程是这样的。但是上边的操作的都是基于Unsafe的,上边的代码是先找到head,而多线程情况下可能head一直在变的缘故吧,这里拿到head之后好像是继续往前走,直到找到真的头节点,然后才进行casPrev操作,成功之后才进行头节点的设置。如果设置失败,也就是当前线程找到的头已经被其他线程修改了,那么就自旋,继续向前找。直到设置成功。通过linkFirst的阅读,那么liskLast的操作应该也差不多

public E pollFirst() {        for (Node
p = first(); p != null; p = succ(p)) { E item = p.item; if (item != null && p.casItem(item, null)) { unlink(p); return item; } } return null; }

    在pollFirst中,首先拿到头接点,然后去判断是否是头节点,如果是的话就将头节点释放,然后将头简单返回。那么pollLast方法也差不多。

  在push、pop提供了栈的用法。toArrayList也没有什么要写的了,都是常规操作。看了看,好像没啥了。好了今天的分析就到这里了。晚安!

      

                                         

转载地址:http://smkmi.baihongyu.com/

你可能感兴趣的文章
yii2 - 增加actions
查看>>
php图像处理函数大全(缩放、剪裁、缩放、翻转、旋转、透明、锐化的实例总结)
查看>>
magento url中 uenc 一坨编码 base64
查看>>
强大的jQuery焦点图无缝滚动走马灯特效插件cxScroll
查看>>
Yii2.0 数据库查询
查看>>
yii2 db 操作
查看>>
mongodb group 有条件的过滤组合个数。
查看>>
关于mongodb的 数组分组 array group
查看>>
MongoDB新的数据统计框架介绍
查看>>
mongodb 增加全文检索索引
查看>>
symfony
查看>>
mysql数据库主从同步的问题解决方法
查看>>
LoadRunner如何在脚本运行时修改log设置选项?
查看>>
QC数据库表结构
查看>>
自动化测试工具的3个关键部分
查看>>
测试工具厂商的编程语言什么时候“退休”?
查看>>
资源监控工具 - Hyperic HQ
查看>>
LoadRunner中Concurrent与Simultaneous的区别
查看>>
SiteScope - Agentless监控
查看>>
使用QTP的.NET插件扩展技术测试ComponentOne的ToolBar控件
查看>>