【高并发系列】20、JDK并发容器 - ConcurrentLinkedQueue

图解Java并发容器: CHM、ConcurrentLinkedQueue、七种阻塞队列的使用场景和原理 / node数组最大容量:2^30=1073741824// 默认初始值,必须是2的幕数//数组可能最大值,需要与toArray()相关方法关联//并发级别,遗留下来的,为兼容以前的版本// 负载因子// 链表转红黑树阀值,> 8 链表转换为红黑树//树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量,<=UNTREEIFY_THRESHOLD 则untreeify(lo)) 阅读详情

ConcurrentLinkedQueue使用链表作为其数据结构,节点Node定义如下:

private static class Node<E> {
    // 目标元素
    volatile E item;
    // 当前节点的下一个元素
    volatile Node<E> next;

    /**
     * Constructs a new node.  Uses relaxed write because item can
     * only be seen after publication via casNext.
     */
    Node(E item) {
        UNSAFE.putObject(this, itemOffset, item);
    }
    // 设置当前Node的item值,cmp为期望值,val为设置目标值;
    // 当当前值等于期望值时,将目标设置为val;
    boolean casItem(E cmp, E val) {
        return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
    }
    
    void lazySetNext(Node<E> val) {
        UNSAFE.putOrderedObject(this, nextOffset, val);
    }
    // 设置next字段值,cmp为期望值,val为设置目标值;
    // 当当前值等于期望值时,将目标设置为val;
    boolean casNext(Node<E> cmp, Node<E> val) {
        return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
    }

    ...
}

ConcurrentLinkedQueue类内部两个重要字段,head和tail,分别表示头部和尾部;

private transient volatile Node<E> head;
private transient volatile Node<E> tail;

无参构造方法初始化head和tail:

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

 向队列添加元素,方法没有锁操作,线程安全由CAS操作和队列的算法来保证;

/**
 * Inserts the specified element at the tail of this queue.
 * As the queue is unbounded, this method will never return {@code false}.
 *
 * @return {@code true} (as specified by {@link Queue#offer})
 * @throws NullPointerException if the specified element is null
 */
public boolean offer(E e) {
    checkNotNull(e);
    final Node<E> newNode = new Node<E>(e);

    for (Node<E> t = tail, p = t;;) {
        Node<E> q = p.next;
        if (q == null) {
            // p is last node
            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
                    casTail(t, newNode);  // Failure is OK.
                return true;
            }
            // Lost CAS race to another thread; re-read next
        }
        else if (p == q)
            // 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.
            p = (t != (t = tail)) ? t : head;
        else
            // Check for tail updates after two hops.
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

核心是for循环,只有添加成功后返回true才能退出循环;

当第一次加入元素时,队列为空,所以p.next为null,进入第一层判断,表明p是最后一个节点,CAS尝试将p的next节点赋值为newNode,即将第一个元素加入队列;如果casNext()失败,循环尝试,直至成功;如果成功,此时p==t,直接返回true,退出方法;所以新增第一个元素时,tail不会被更新;

当增加第二个元素时,tail没有被更新,p=t=tail=head,tail还指向head节点位置,所以p.next实际指向的是第一个元素,此时q!=null&p!=q,所以跳转到最后else语句中,因为p==t,给p赋新值,即p=q,p实际指向了第一个元素,此时进入下一层循环时,p.next为null,进入第一层判断,CAS尝试将p的next节点赋新值newNode,如果casNext()失败,循环尝试直到成功;如果成功,此时p指向第一个元素,而t指向head节点,所以p!=t,尝试更新tail,即将tail由t值所在位置(此时为head节点)更新为新加入元素(第二个元素);

当p==q时,此节点为哨兵节点,next指向自己,这个节点已经不在队列中存在了,表示要删除的节点或空节点;此时无法通过next获得后续节点,所以要返回head节点(所有的节点都可以从head节点获得);一旦在执行过程中发生tail节点被其他线程修改的情况,则使用新的tail作为链表末尾,避免了从head节点重新查找tail的开销;

“!=”不是原子操作,可以被中断,在执行“!=”操作时,会先取得t值,再执行t=tail,取得t的新值,然后再进行比较两个值是否相等;

p = (t != (t = tail)) ? t : head;

哨兵节点产生情况:

ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.add("1");
queue.poll();
queue.add("2");

poll()方法源码如下:

public E poll() {
    restartFromHead:
    for (;;) {
        for (Node<E> h = head, p = h, q;;) {
            E item = p.item;

            if (item != null && p.casItem(item, null)) {
                // Successful CAS is the linearization point
                // for item to be removed from this queue.
                if (p != h) // hop two nodes at a time
                    updateHead(h, ((q = p.next) != null) ? q : p);
                return item;
            }
            else if ((q = p.next) == null) {
                updateHead(h, p);
                return null;
            }
            else if (p == q)
                continue restartFromHead;
            else
                p = q;
        }
    }
}

队列中只有1个元素,此时tail并没有更新,指向和head相同的位置;此时head本身的item为null,next指向第一个元素;

在第一次循环时,代码直接进入最后一个else语句,p=q,此时p、q指向第一个元素;

第二轮循环中,p.item为“1”,不为null,如果CAS操作成功,进入第一个if语句中,p.casItem()将p的item设置为null(删除操作,将第一个元素置空);此时h指向head,而p指向刚刚置空的第一个元素,所以不相等;p.next为null,赋值null给q,((q = p.next) != null) ? q : p)返回p,进入updateHead(h,p)方法;

/**
 * Tries to CAS head to p. If successful, repoint old head to itself
 * as sentinel for succ(), below.
 */
final void updateHead(Node<E> h, Node<E> p) {
    if (h != p && casHead(h, p))
        h.lazySetNext(h);
}

可以看到,通过casHead(h,p)将p作为新的链表头部,而原有的head就被h.lazySetNext(h)设置为哨兵节点;

此时原有的head头部和tail实际上是同一个元素,因此,再次用offer()方法插入元素时,就会遇到这个哨兵节点;

 

【Java多线程】JUC之并发容器—深入剖析并发队列ConcurrentLinkedQueue(九) **ConcurrentLinkedQueue**:是一个适用于**高并发场景**,基于`单向链表`实现的`无界非阻塞线程安全队列`,使用`CAS自旋+volatile`来包保证线程安全。按照`FIFO`的方式对存储元素进行排序,该队列`不允许写入null元素`。 - 由于是完全基于`无锁算法`实现的,多线程同时进行修改队列的操作(比如同时入队),很可能出现`CAS修改失败`的情况,那么失败的线程`会进入下一次自旋,再尝试入队操作,直到成功`。**所以,在并发量适中的情况下,ConcurrentLinked 阅读详情

相关推荐

OpenClaw + Ollama 本地大模型部署教程:彻底告别Token焦虑

OpenClaw + Ollama 本地大模型部署教程:彻底告别Token焦虑

leaning_java的博客 1228

并发编程ConcurrentLinkedQueue使用示例详解

构造函数中,新建了一个“内容为null的节点”,并设置表头head和表尾tail的值为新节点。head和tail是volatile类型,具有volatile赋予的含义:“即对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入”。Node中操作节点数据的API,是通过Unsafe机制的CAS函数实现的;offer(E e)的作用就是将元素e添加到链表的末尾。poll()的作用就是删除链表的表头节点,并返回被删节点对应的值。poll():在链表头部获取并且移除一个元素。

Free雅轩的博客 2121

LC-3 机器语言 指令集

生活本就沉闷,但跑起来就有风

笔记 1万+

Java多线程--队列(Queue)--使用/教程/实例

原文网址: 简介 本文用示例来介绍Java中的队列的用法。包括:DelayQueue,ConcurrentLinkedQueue,BlockingQueue。 文章最后有一个常见的面试题代码实例:机器要对手机按顺序做如下任务:生产、打包、发货。消费者等待收货,如何使用BlockingQueue完成? DelayQueue 简介 DelayQueue:只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue就是基于Priority...

IT利刃出鞘的博客 7366

2024年数字IC秋招-睿思芯科-IC验证工程师-笔试题

公司介绍:初创公司,做risc-v,公司位于深圳笔试题型:基础题(10个) + 选做题(5个)笔试平台:showmebug(电脑监控)笔试时间:1h个人感受:每年rsxk的题目好像是一样的,起码今年和去年的题目是相同的,但是做完笔试题目就没有消息了。

qq_42135020的博客 1582

【Unity 实用工具篇】 | Book Page Curl 快速实现翻书效果

游戏和应用开发中,真实感的交互设计能显著提升用户体验。Unity3D图书翻页效果插件为开发者提供了一种便捷方式,可在项目中实现高度拟真的书籍翻页动画。 本文介绍该插件的快速使用方法,涵盖从基础导入到自动翻页效果创建的全过程,让我们快速实现翻页效果。

努力前行,总会成为自己心中的那道光 1万+

一、github

一、github简介注册账户GitHub访问过慢 简介 ​ gitHub是一个面向开源及私有软件项目的托管平台,因为只支持git 作为唯一的版本库格式进行托管,故名gitHub。 ​ gitHub于2008年4月10日正式上线,除了git代码仓库托管及基本的 Web管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。目前,其注册用户已经超过350万,托管版本数量也是非常之多,其中不乏知名开源项目 Ruby on Rails、jQuery、pytho

一个孤独漫步者的遐想的博客 1万+

ardupilot开发 --- 避障篇

适配ardupilot的雷达避障功能开发,使用低成本雷达思岚科技系列雷达,配置简单,灵敏度高,障碍物检测范围不低于30m。

记录老板的每次pua,分享领导每天的毒鸡汤... 4301

超详细GitHub注册和登录教程

GitHub 是程序员开源精神之所系。在这个神奇的开源社区,职业程序员和编程爱好者畅所欲言,探寻自己感兴趣的项目、分享源代码、交流学习。最近看到好多朋友私信我,让我出一个详细的githup注册和登录的教程,现在它来啦!

FMgxth的博客 6万+

YOLOv11改进 | 引入DEConv细节增强卷积模块:恢复细节信息,提升小目标检测精度

摘要: YOLOv11在目标检测中面临小目标检测精度不足的问题,主要由于细节信息丢失。本文提出引入DEConv(细节增强卷积)模块,通过多分支卷积和残差连接恢复高频细节,提升小目标检测性能。DEConv结合3×3卷积与空洞卷积增强边缘信息,并自适应融合特征,兼容YOLOv11的P3层(小目标关键层)且计算高效。实验表明,该改进在无人机航拍、显微镜图像等密集小目标场景中显著减少漏检,同时保持轻量化和实时性优势。代码实现可直接嵌入现有YOLO架构,仅需替换P3层卷积模块。

走向CTO的路上... 950

uniapp:实现APP获取陀螺仪数据、加速传感器、线性加速传感器,uniApp实时监听陀螺仪【XYZ】数据及停止监听与资源释放等功能示例源码,根据需求做功能(完美兼容Android安卓、苹果ios)

uniapp陀螺仪,uniapp加速传感器,uniapp线性加速传感器,APP陀螺仪数据,uniapp app 端获取陀螺仪数据示例代码,手机中获取陀螺仪的数据,uniApp实时获取陀螺仪xyz数据,android陀螺仪方向,获得陀螺仪传感器,处理传感器数据,停止监听与资源释放,Android使用陀螺仪,uniapp苹果ios陀螺仪,uni-app设备之(加速计,罗盘,陀螺仪)插件安卓/IOS APP提供了获取陀螺仪数据的能力,手机陀螺仪传感器获得手机自身旋转的角度,陀螺仪获取旋转角度,陀螺仪、加速度传感器

✅ 精选推荐文章 4268

大牛公司github官网整理

1.Google 1.Googlehttps://github.com/google2.Google Sampleshttps://github.com/googlesamples3.Google Codelabshttps://github.com/googlecode...4.GoogleChromehttps://github.co...

weixin_34166847的博客 1567

掌握C++新特性中智能指针的综合指南

本文是一篇关于C++中智能指针的综合指南,旨在深入探讨现代内存管理的核心概念和技术。智能指针是C++中一种强大的工具,可以帮助开发人员更安全、高效地管理内存资源。通过详细解析各种类型的智能指针,包括unique_ptr、shared_ptr和weak_ptr,读者将了解它们的原理、用法和适用场景。本文将重点介绍智能指针的优势,如自动内存释放、避免内存泄漏和循环引用等问题。此外,还将讨论智能指针与传统指针的区别,并提供一些最佳实践和实用技巧,帮助读者在实际项目中正确、高效地使用智能指针。

Lion_Long的博客 1200

Java 并发容器(含 ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue、BlockingQueue等)

JDK提供的这些容器大部分在包中。线程安全的 HashMap线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector.高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。跳表的实现。这是一个 Map,使用跳表的数据结构进行快速查找。在很多应用场景中,读操作可能会远远大于写操作。

rc1596919047的博客 1504

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

JAVA高并发——JDK的并发容器

并发集合、深入解析ConcurrentHashMap、深度剖析ConcurrentLinkedQueue类、不变模式下的CopyOnWriteArrayList类、数据共享通道:BlockingQueue、跳表(SkipList)

码说芯语的博客 1343

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

JDK 提供的并发容器总结

JDK 提供的这些容器大部分在 java.util.concurrent 包中。 ConcurrentHashMap: 线程安全的 HashMap CopyOnWriteArrayList: 线程安全的 List,在读多写少的场合性能非常好,远远好于 Vector. ConcurrentLinkedQueue: 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。 BlockingQueue: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻

qq_21010647的博客 715

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

高并发之并发容器详解

高并发之并发容器详解 一、vector Vector 是矢量队列,它是JDK1.0版本添加的类。继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。 Vector 继承了AbstractList,实现了List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。 Vector 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可

Noaman_Chen的博客 658

这些Java并发容器,你都了解吗?

在多线程环境下,数据的并发访问和修改是无法避免的问题。为了解决这个问题,Java 提供了一系列并发容器,这些容器在内部已经处理了并发问题,使得我们可以在多线程环境下安全地访问和修改数据。从上面的介绍总总结有以下几种容器类ConcurrentHashMap:并发版 HashMapCopyOnWriteArrayList:并发版 ArrayListCopyOnWriteArraySet:并发 SetConcurrentLinkedQueue:并发队列 (基于链表)

Venus's Blog 1万+

JDK并发容器总结

JDK并发容器总结概述ConcurrentHashMap实现原理ConcurrentHashMap 和 HashTable 的区别CopyOnWriteArrayList简介实现方式源码分析读取操作写入操作ConcurrentLinkedQueueBlockingQueueArrayBlockingQueueLinkedBlockingQueuePriorityBlockingQueueConcu...

JustinNeil的博客 443

java高并发编程——JDK并发容器

一、并发集合简介   JDK的并发容器大部分都在java.util.concurrent包中。 1.ConcurrentHashMap:一个高效的HashMap,可以理解为线程安全的HashMap。 2.CopyOnWrtieArrayList:与ArrayList相似,在读多谢少的场景中,性能比Vector好很多。 3.ConcurrentLinkedQueue:高效的并发队列,使用链表实现,可...

qq_34459728的博客 282

并发容器之ConcurrentLinkedQueue详解

简介 在并发编程中我们有时候需要使用线程安全的队列。如果我们要实现一个线程安全的队列有两种实现方式一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现,而非阻塞的实现方式则可以使用循环CAS的方式来实现,下面我们一起来研究下Doug Lea是如何使用非阻塞的方式来实现线程安全队列ConcurrentLinkedQueue的。 ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进

GSON的博客 1878

JDK 并发容器总结

一 JDK 提供的并发容器总结 《实战Java高并发程序设计》为我们总结了下面几种大家可能会在高并发程序设计中经常遇到和使用的 JDK 为我们提供的并发容器。先带大家概览一下,下面会一一介绍到。 JDK提供的这些容器大部分在 java.util.concurrent 包中。 ConcurrentHashMap:线程安全的HashMap CopyOnWriteArrayList...

xiaoyaGrace的博客 155

Java-常见并发容器-总结

在很多应用场景中,读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据,因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问List的内部数据,毕竟读取操作是安全的。这和我们之前提到过的读写锁的思想非常类似,也就是读读共享、写写互斥、读写互斥、写读互斥。JDK 中提供了类比相比于在读写锁的思想又更进一步。为了将读取的性能发挥到极致,读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来,读操作的性能就会大幅度提升。

Java程序员的知识博客 1174

高并发编程_高并发编程【并发容器篇】

摘要:本文主要介绍开发中常用的并发容器,并发容器指的是:在高并发场景下,仍然保持良好读写性能的容器,此外还接一个完美实现消费者生产者模型的工具(BlockingQueue)。1. 集合包装HashMap不是线程安全的,在多线程环境下,直接使用HashMap,不做同步控制,那么HashMap的数据会发生一些稀奇古怪的问题。因此在要求数据一致的前提下,不能直接使用HashMap。可以使用线程安全版本的...

weixin_32391045的博客 105

Java并发编程——并发容器

Java提供的并发容器详解

m0_62909831的博客 1209

Java 常见并发容器总结

对于一个单链表,即使链表是有序的,如果我们想要在其中查找某个数据,也只能从头到尾遍历链表,这样效率自然就会很低,跳表就不一样了。的另外一个不同之处是:哈希并不会保存元素的顺序,而跳表内所有的元素都是排序的。跳表内的所有链表的元素都是排序的。的内容时,不会直接修改原数组,而是会先创建底层数组的副本,对副本数组进行修改,修改完之后再将修改后的数组赋值回去,这样就可以保证写操作不会影响读操作了。适合在对性能要求相对较高,同时对队列的读写存在多个线程同时进行的场景,即如果对队列加锁的成本较高则适合使用无锁的。

aaaa_1111111的博客 948
上一篇: 【高并发系列】19、JDK并发容器 - 线程安全的HashMap、List
下一篇: 【高并发系列】21、JDK并发容器 - 高效读取 CopyOnWriteArrayList
hellboy0621
博客等级 码龄9年 61粉丝 147原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值