并发队列中迭代器弱一致性原理探究

一、前言

并发队列里面的Iterators是弱一致性的,next返回的是队列某一个时间点或者创建迭代器时候的状态的反映。当创建迭代器后,其他线程删除了该元素时候并不会抛出java.util.ConcurrentModificationException异常,能够保持创建迭代器后的元素一定被正确的next出来。

二、 ConcurrentLinkedQueue类图结构

0?wx_fmt=png

以ConcurrentLinkedQueue为例说下是如何实现的,如图内部的Itr类实现了接口Iterator的功能。

三、测试代码

3.1 实验一

本实验是测试获取迭代器后调用next前删除元素看看会有什么结果

0?wx_fmt=png

主线程debug断点查看图:

0?wx_fmt=png

如图主线程获取了队列元素zlx节点的迭代器,在调用next的时候debug阻塞主。

下面激活SleepInterrupt线程,执行remove操作:0?wx_fmt=png

remove后调度到主线程执行0?wx_fmt=png

可知还是迭代出来了已经删除的元素,并且没抛出异常。

3.2 试验2

本实验测试获取迭代器前调用next后删除迭代器后面的元素看看有什么结果0?wx_fmt=png

主线程debug断点图0?wx_fmt=png

下面激活SleepInterrupt线程,执行remove操作:0?wx_fmt=png

remove后调度到主线程执行0?wx_fmt=png

可知现在队列里面没有了gh元素,下面看看迭代结果

3.3 试验3

本实验测试获取迭代器前调用next后删除迭代器后面的元素看看有什么结果0?wx_fmt=png

下面看看迭代结果0?wx_fmt=png

四、源码分析

首先调用队列的iterator()方法时候会实例化一个迭代器,所以每次调用该方法都是一个新的实例,构造函数内部调用了advance方法,目的是确定第一个元素的iterator.

Itr() {
    advance();//(1)}//获取队列中下一个可用节点。调用next()时候返回节点值,或者返回nullprivate E advance() {    //lastRet记录调用最后一次调用next时候的节点
    lastRet = nextNode;(2)    //x存放节点值
    E x = nextItem;(3)    //获取next节点
    Node<E> pred, p;    //如果为nul则调用阻塞队列的first方法获取
    if (nextNode == null) {
        p = first();//(4)
        pred = null;
    } else {        //不为nul则获取下一个节点(5)
        pred = nextNode;
        p = succ(nextNode);
    }    for (;;) {        //p=null则直接返回,重置节点null(6)
        if (p == null) {
            nextNode = null;
            nextItem = null;            return x;
        }        //否者记录当前节点并返回值
        E item = p.item;        if (item != null) {//(7)
            nextNode = p;
            nextItem = item;            return x;
        } else {            // 跳过null值节点(8)
            Node<E> next = succ(p);            if (pred != null && next != null)
                pred.casNext(p, next);
            p = next;
        }
    }
}
//判断是否有原始public boolean hasNext() {    return nextNode != null;
}//有则删除public E next() {    if (nextNode == null) throw new NoSuchElementException();    return advance();
}//删除元素public void remove() {
    Node<E> l = lastRet;    if (l == null) throw new IllegalStateException();    // rely on a future traversal to relink.
    l.item = null;
    lastRet = null;
}

下面看图说话:

0?wx_fmt=png

那么调用队列的iterator时候执行(1)(4)(7)后队列状态图:0?wx_fmt=png

调用hasNext()时候知道nextNode != null所以返回true.

然后调用next()方法执行(2)(5)(7)后,返回zlx,队列状态图0?wx_fmt=png

也就说第一次调用队列的iterator方法会在构造函数调用advance方法一次,这时候已经把队列第一个可用的节点指针赋值给nextNode,节点值赋值给nextItem;这样当调用hasNext时候先看nextNode是否null,null说明队列为空则返回false说明队列里面没有元素,否者会调用next方法,该方法会再次调用advance方法,由于调用hasNext确定了nextNode不为null所以会调用(5)来获取下次调用next要返回的值,也就是当前nextNode的后继节点。如果后继节点为null则返回nextNode对应的值nextItem,否者设置下一次调用next时候需要的nextNode和nextItem。

下面考虑下实验一的情况,首先线程1调用调用hasNext()后情况为:0?wx_fmt=png

0?wx_fmt=png

现在在调用next方法(2)(5)(7)后的状态为:0?wx_fmt=png

试验2的结果很明显,这里不再说了,下面看看试验3

0?wx_fmt=png

然后调用remove方法因为lastRet=null所以抛出了异常,其实应该先调用next方法在调用remove方法。0?wx_fmt=png

然后看remove里面并没有看到有对队列里面的头尾节点进行操作,也就说并没有在队列中移除该元素的操作,乍一看这有问题,但是没问题:下面看删除zlx后队列状态0?wx_fmt=png

也就是remove仅仅把节点内容变为null,所以head还是指向这个元素(注意本节讲的都是不带哨兵节点的队列,正常情况下队列一开始有个null的哨兵节点,如果本节考虑的话,那么上面的图应该有两个null节点,一个是哨兵,一个是zlx节点变成的)

而poll时候如果节点内容为null则会继续查看后继节点,所以这里remove简单的把节点内容变为null即可。

四、总结

并发队列里面的迭代器通过使用nextItem保留创建迭代器时候的节点的值,保证了在调用hasNext和next方法之间其他线程删除该元素后还可以正常返回删除节点的内容,并不抛出异常,之所以说是弱一致性是因为调用next时候该元素已经不在队列里面了,但是迭代返回还可以返回。另外remove操作并没有立刻把删除的原始从队列中干掉,而是在出队时候从队列里面解除,让它变为自引用节点,等待被垃圾回收。

如果感觉写的好,就打赏下小编吧^-^

0?wx_fmt=jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
[JAVA工程师必会知识点之并发编程]1、现在几乎100%的公司面试都必须面试并发编程,尤其是互联网公司,对于并发编程的要求更高,并发编程能力已经成为职场敲门砖。2、现在已经是移动互联和大数据时代,对于应用程序的性能、处理能力、处理时效性要求更高了,传统的串行化编程无法充分利用现有的服务器性能。3、并发编程是几乎所有框架的底层基础,掌握好并发编程更有利于我们学习各种框架。想要让自己的程序执行、接口响应、批处理效率更高,必须使用并发编程。4、并发编程是高级程序员的标配,是拿高薪的必备条件。 【主讲讲师】尹洪亮Kevin:现任职某互联网公司首席架构师,负责系统架构、项目群管理、产品研发工作。10余年软件行业经验,具有数百个线上项目实战经验。擅长JAVA技术栈、高并发高可用伸缩式微服务架构、DevOps。主导研发的蜂巢微服务架构已经成功支撑数百个微服务稳定运行【推荐你学习这门课的理由:知识体系完整+丰富学习资料】1、 本课程总计122课时,由五大体系组成,目的是让你一次性搞定并发编程。分别是并发编程基础、进阶、精通篇、Disruptor高并发框架、RateLimiter高并发访问限流吗,BAT员工也在学。2、课程附带附带3个项目源码,几百个课程示例,5个高清PDF课件。3、本课程0基础入门,从进程、线程、JVM开始讲起,每一个章节只专注于一个知识点,每个章节均有代码实例。 【课程分为基础篇、进阶篇、高级篇】一、基础篇基础篇从进程与线程、内存、CPU时间片轮训讲起,包含线程的3种创建方法、可视化观察线程、join、sleep、yield、interrupt,Synchronized、重入锁、对象锁、类锁、wait、notify、线程上下文切换、守护线程、阻塞式安全队列等内容。二、进阶篇进阶篇课程涵盖volatied关键字、Actomic类、可见性、原子性、ThreadLocal、Unsafe底层、同步类容器、并发类容器、5种并发队列、COW容器、InheritableThreadLocal源码解析等内容。三、精通篇精通篇课程涵盖JUC下的核心工具类,CountDownLath、CyclicBarrier、Phaser、Semaphore、Exchanger、ReentrantLock、ReentrantReadWriteLock、StampedLock、LockSupport、AQS底层、悲观锁、乐观锁、自旋锁、公平锁、非公平锁、排它锁、共享锁、重入锁、线程池、CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor、自定义线程池、ThreadFactory、线程池切面编程、线程池动态管理等内容,高并发设计模式,Future模式、Master Worker模式、CompletionService、ForkJoin等课程还包含Disruptor高并发无锁框架讲解:Disruptor支持每秒600万订单处理的恐怖能力。深入到底层原理和开发模式,让你又懂又会用。高并发访问限流讲解:涵盖木桶算法、令牌桶算法、Google RateLimiter限流开发、Apache JMeter压力测试实战。 【学完后我将达到什么水平?】1、 吊打一切并发编程相关的笔试题、面试题。2、 重构自己并发编程的体系知识,不再谈并发色变。3、 精准掌握JAVA各种并发工具类、方法、关键字的原理和使用。4、 轻松上手写出更高效、更优雅的并发程序,在工作能够提出更多的解决方案。  【面向人群】1、 总感觉并发编程很难、很复杂、不敢学习的人群。2、 准备跳槽、找工作、拿高薪的程序员。3、 希望提高自己的编程能力,开发出更高效、性能更强劲系统的人群。4、 想要快速、系统化、精准掌握并发编程的人群。【课程知识体系图】
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值