科普文:JUC系列之ConcurrentLinkedQueue非阻塞队列用法

news2024/9/29 19:21:55

概叙

     **Queue接口与List、Set同一级别,都是继承了Collection接口**。队列是一种数据结构.它有两个基本操作:在队列尾部加人一个元素,和从队列头部移除一个元素,队列以一种先进先出的方式管理数据。

       队列分为两种,阻塞队列和非阻塞队列。阻塞队列是如果你试图向一个 已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元索,将导致消费者线程或者生产者线程阻塞,非阻塞队列是能即时返回结果(消费者),但必须自行编码解决返回为空的情况处理(以及消费重试等问题)。这两种都是线程安全的。

​1.阻塞队列BlockingQueue ArrayBlockingQueue,DelayQueue,LinkedBlockingDeque,LinkedBlockingQueue,LinkedTransferQueue,PriorityBlockingQueue,SynchronousQueue

2.非阻塞队列 ConcurrentLinkedDeque,ConcurrentLinkedQueue

理论上非阻塞队列更高效,但是实际应用中阻塞队列已经可以应付大多数的并发了。 ​

并编程中,一般需要用到安全的队列,如果要自己实现安全队列,可以使用2种方式:

  1. 加锁,这种实现方式就是我们常说的阻塞队列。
  2. 使用循环CAS算法实现,这种方式实现队列称之为非阻塞队列。

加锁队列的实现较为简单,这里就略过,我们来重点来解读一下非阻塞队列。 从点到面, 下面我们来看下非阻塞队列经典实现类:ConcurrentLinkedQueue (JDK1.8版)  

看下ConcurrentLinkedQueue的结构图

从内图可以了解ConcurrentLinkedQueue一个大概,ConcurrentLinkedQueue内部持有2个节点:head头结点,负责出列, tail尾节点,负责入列。

而元素节点Node,使用item存储入列元素,next指向下一个元素节点。

1

2

3

4

5

private static class Node<E> {

    volatile E item;

    volatile Node<E> next;

    //....

}

1

2

3

4

5

6

public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>

        implements Queue<E>, java.io.Serializable {

        private transient volatile Node<E> head;

        private transient volatile Node<E> tail; 

        //....

}

 ConcurrentLinkedQueue使用特点

  • 不允许null入列
  • 在入队的最后一个元素的next为null
  • 队列中所有未删除的节点的item都不能为null且都能从head节点遍历到
  • 删除节点是将item设置为null, 队列迭代时跳过item为null节点
  • head节点跟tail不一定指向头节点或尾节点,可能存在滞后性

之所以有这奇葩约定,全因ConcurrentLinkedQueue是并发非阻塞队列决定的。 我们从源码上看一下ConcurrentLinkedQueue实现过程

ConcurrentLinkedQueue源码详解

入列offer

我们印象中链表特点:tail节点表示最后一个节点, head表示第一个节点。ConcurrentLinkedQueue 跟传统的链表有点区别,在单线程环境下符合传统链表特点,但涉及到多线程环境,ConcurrentLinkedQueue 中的tail节点不一定是最后一个节点,他可能是倒数第二个。所以ConcurrentLinkedQueue判断队尾条件是节点的next为null。

public boolean offer(E e) {

     checkNotNull(e);   //为空判断,e为null是抛异常

     final Node<E> newNode = new Node<E>(e); //将e包装成newNode

     for (Node<E> t = tail, p = t;;) {  //循环cas,直至加入成功

         //t = p = tail

         Node<E> q = p.next;

         if (q == null) {   //判断p是否为尾节点

             //如果是,p.next = newNode

             if (p.casNext(null, newNode)) {

                 //首次添加时,p 等于t,不进行尾节点更新,所以所尾节点存在滞后性 

                 //并发环境,可能存添加/删除,tail就更难保证正确指向最后节点。

                 if (p != t)

                     //更新尾节点为最新元素

                     casTail(t, newNode); 

                 return true;

             }

         }

         else if (p == q)

             //当tail不执行最后节点时,如果执行出列操作,很有可能将tail也给移除了   

             //此时需要对tail节点进行复位,复位到head节点

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

         else

             //推动tail尾节点往队尾移动

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

     }

 }

分析

1、初始化

 2、添加A元素

 3、添加B元素

 4、添加C

从图上看tail不一定执行最后一个节点,但可以确定最后节点的next节点为null。

到这可能朋友问他,并发环境什么情况都有可能,ConcurrentLinkedQueue是怎么保证线程安全的? 我们观察offer方法的设计,

1:是一个死循环,就是不停使用cas判断直到添加元素入队成功。

1

for (Node<E> t = tail, p = t;;)

2:2个cas判断方法 p.casNext(null, newNode) 确保队列在入列时是原子操作

1

2

3

private boolean casTail(Node<E> cmp, Node<E> val) {

    return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);

}

casTail(t, newNode); 确保tail队尾在移动改变时是原子操作

1

2

3

boolean casNext(Node<E> cmp, Node<E> val) {

    return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);

}

而在并发环境,ConcurrentLinkedQueue入列线程安全考虑具体可分2类:

1>线程1线程2同时入列 这个好理解, 线程1,线程2不管在offer哪个位置开始并发,他们最终的目的都是入列,也即都需要执行casNext方法, 我们只需要确保所有线程都有机会执行casNext方法,并且保证casNext方法是原子操作即可。casNext失败的线程,可以进入下一轮循环,人品好的话就可以入列,衰的话继续循环

2>线程1遍历,线程2入列 ConcurrentLinkedQueue 遍历是线程不安全的, 线程1遍历,线程2很有可能进行入列出列操作, 所以ConcurrentLinkedQueue 的size是变化。换句话说,要想安全遍历ConcurrentLinkedQueue 队列,必须额外加锁。

但换一个角度想, ConcurrentLinkedQueue 的设计初衷非阻塞队列,我们更多关注入列与出列线程安全,这2点能保证就可以啦。

出列poll

public E poll() {

    restartFromHead:

    for (;;) {

        for (Node<E> h = head, p = h, q;;) {

            //入列折腾的tail,那出列折腾的就是head

            E item = p.item;

            //出列判断依据是节点的item=null

            //item != null, 并且能将操作节点的item设置null, 表示出列成功

            if (item != null && p.casItem(item, null)) {

                if (p != h)

                    //一旦出列成功需要对head进行移动

                    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

                //推动head节点移动

                p = q;

        }

    }

}

看图, 被移动的节点(item为null的节点)会被jvm回收。

但是有个问题, tail也被回收, 那ConcurrentLinkedQueue就没有tail节点了。

如果此时再添加一个D元素时,会出现什么情况?

 好问的朋友,又想了,ConcurrentLinkedQueue怎么保证出列线程安全?道理跟之前入列一样,cas保证原子操作即可。

ConcurrentLinkedQueue使用示例

阻塞队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。
而非阻塞的实现方式则可以使用循环CAS的方式来实现。因为采用CAS操作,允许多个线程并发执行,并且不会因为加锁而阻塞线程,使得并发性能更好。
示例:

package yxxy.queuue;

import java.util.Arrays;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import com.sun.corba.se.impl.protocol.JIDLLocalCRDImpl;

public class TEST1 {

    private static ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<Integer>();

    public static void main(String[] args) throws InterruptedException {
        
        
        Thread[] ths = new Thread[6];
        for(int i=1;i<ths.length;i++){
            Runnable task = new Runnable() {
                
                @Override
                public void run() {
                    try {
                        while (!queue.isEmpty()) {
                            int i = queue.poll();
                        }
                        
                    } catch (Exception e) {
                        System.out.println();
                    }
                    
                }
                
            };
            ths[i] = new Thread(task);
        }
        Runnable task = new Runnable() {
            
            @Override
            public void run() {
                for(int i=0;i<99999;i++){
                    try {
                        queue.offer(i);
                    } catch (Exception e) {
                    }
                }
            }
            
        };
        ths[0] = new Thread(task);
        long timeStart = System.currentTimeMillis();
        runAndComputeTime(ths);
        System.out.println("cost time " + (System.currentTimeMillis() - timeStart) + "ms");
    }
    
    static void runAndComputeTime(Thread[] ths) {
        Arrays.asList(ths).forEach(t->t.start());
        Arrays.asList(ths).forEach(t->{
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
    }
}

当消费者速度大于生产者并且生产者速度不变的情况下,处理瓶颈就处在了消费者线程分别取队列里元素的速度上,所以此时使用不加锁的方式来取队列的元素会大大提高处理速度。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1980254.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pikachu漏洞平台~文件包含漏洞

在PHP程序中使用文件包含的对象可以被前端的用户控制且没有经过过滤或严格的定义&#xff0c;用户可以将其他的文件作为参数带入到PHP代码中解释执行&#xff0c;从而造成敏感信息泄露/程序文件读取/GetShell等危害的漏洞。 0x01文件包含漏洞 连接成功&#xff0c;完毕&#x…

聚焦IOC容器刷新环节postProcessBeanFactory(BeanFactory后置处理)专项

目录 一、IOC容器的刷新环节快速回顾 二、postProcessBeanFactory源码展示分析 &#xff08;一&#xff09;模版方法postProcessBeanFactory &#xff08;二&#xff09;AnnotationConfigServletWebServerApplicationContext 调用父类的 postProcessBeanFactory 包扫描 …

oracle(19c)用户管理

简介 本文介绍 Oracle 中的用户管理&#xff0c;包含以下内容&#xff1a; 概念介绍 系统用户 解锁 hr 用户 创建用户 用户相关案例 使用 Profile 管理用户口令 Oracle 的认证方式 重置管理员(sys)密码 1. 概念介绍 使用前可以自行安装oracle数据库 oracle19c安装&a…

【系统架构设计师】二十四、安全架构设计理论与实践④

目录 六、数据库系统的安全设计 6.1 数据库的完整性设计 6.1.1 数据库完整性设计原则 6.1.2 数据库完整性的作用 6.1.3 数据库完整性设计示例 七、系统架构的脆弱性分析 7.1 软件脆弱性的特点和分类 7.2 软件脆弱性的生命周期 7.2.1 脆弱性的引入阶段 7.2.2 产生破坏…

pythonflaskMYSQL自驾游搜索系统32127-计算机毕业设计项目选题推荐(附源码)

目 录 摘要 1 绪论 1.1研究背景 1.2爬虫技术 1.3flask框架介绍 2 1.4论文结构与章节安排 3 2 自驾游搜索系统分析 4 2.1 可行性分析 4 2.2 系统流程分析 4 2.2.1数据增加流程 5 2.3.2数据修改流程 5 2.3.3数据删除流程 5 2.3 系统功能分析 5 2.3.1 功能性分析 6 2.3.2 非功…

C++初阶学习——探索STL奥秘——模拟实现string类

1、string类的构造 上面的代码从表面看没什么问题&#xff0c;但是运行后会发现程序有多处bug 但是如上图一样&#xff0c;这样改进依然有bug 因为我们编写无参构造函数的时候&#xff0c;肯定要让_str默认为nullptr&#xff0c;但是这样的话&#xff0c;在main函数中创建对象…

使用npm全局安装typescript

查看npm安装 npm -v 安装typescript npm i -g typescript 查看安装 tsc 这就是标致着安装完成。

uBlock Origin很快将无法在Chrome上使用 开发者发布情况说明

Chrome v127 版开始扩展程序页面将自动显示即将不再支持的扩展程序&#xff0c;包括知名的广告拦截扩展程序 uBlock Origin 也在谷歌的警告列表中。昨天 uBO 团队发布新的支持文档对目前的情况进行说明&#xff0c;简单来说就是 Chrome 将不再支持基于 Manifest v2 开发的扩展程…

【设计模式入门】设计模式全解析:23种经典模式介绍与评级指南(设计师必备)

文章目录 设计模式简介引言七项基本原则创建型模式单例模式&#xff08;Singleton&#xff09;工厂方法模式&#xff08;Factory Method&#xff09;抽象工厂模式&#xff08;Abstract Factory&#xff09;建造者模式&#xff08;Builder&#xff09;原型模式&#xff08;Proto…

文件夹图标变白色无法打开:高效数据恢复指南

在日常使用电脑的过程中&#xff0c;我们可能会遇到一种令人困扰的情况——文件夹图标突然变成白色且无法正常打开。这一现象不仅影响了文件管理的便捷性&#xff0c;还可能意味着重要数据的丢失风险。本文将深入探讨这一现象的原因&#xff0c;并提供一种专业且高效的数据恢复…

一二三应用开发平台应用开发示例(11)——收藏夹功能高代码改造及总结

背景 前面使用低代码配置&#xff0c;把实体配置、库表和模式化的代码生成出来了&#xff0c;实际上是用平台帮忙开发人员把“体力活”给干了。接下来&#xff0c;就需要在此基础上进行个性化逻辑的开发。 文档收藏 完善实体模型 文档收藏夹我们原先配置了三个关键属性&…

【Mind+】掌控板入门教程04 迷你动画片

还记得小时候每天放学必看的动画片吗&#xff1f;还记得那些年陪伴我一起长大的卡通人物吗&#xff1f;勇救爷爷的葫芦娃&#xff0c;我们的朋友小哪吒&#xff0c;相信这些经典的动画形象已经成为了一代人童年的美好回忆。今天就让我们用掌控板来制作一部迷你动画片吧。 项目示…

stm32入门学习10-I2C和陀螺仪模块

&#xff08;一&#xff09;I2C通信 &#xff08;1&#xff09;通信方式 I2C是一种同步半双工的通信方式&#xff0c;同步指的是通信双方时钟为一个时钟&#xff0c;半双工指的是在同一时间只能进行接收数据或发送数据&#xff0c;其有一条时钟线&#xff08;SCL&#xff09;…

代码随想录——买卖股票的最佳时机 IV(Leetcode 188)

题目链接 动态规划 class Solution {public int maxProfit(int k, int[] prices) {int[][] dp new int[prices.length][2 * k 1];// 初始化for(int i 1; i < 2 * k 1; i i 2){dp[0][i] -prices[0];}// dp更新寻找最大利润for(int i 1; i < prices.length; i){…

使用Halcon变换与校正图像

使用Halcon变换与校正图像 文章目录 使用Halcon变换与校正图像1. 二维图像的平移、旋转和缩放1.图像的平移2.图像的旋转3.图像的缩放2. 图像的仿射变换3. 投影变换4 实例&#xff1a;透视形变图像校正 由于相机拍摄的时候可能存在角度偏差&#xff0c;因此实际获得的画面可能会…

(C23/C++23) 语句末尾的标签

文章目录 &#x1f516;前言&#x1f3f7;️ref&#x1f3f7;️标号 &#x1f516;兼容&#x1f3f7;️23标准前&#x1f3f7;️23标准后&#x1f3f7;️原因 &#x1f516;未兼容&#x1f516;END&#x1f31f;关注我 &#x1f516;前言 &#x1f3f7;️ref C23提案复合语句末…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 两数之和绝对值最小(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM金牌🏅️团队| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题…

基于机器学习和深度学习的时间序列分析和预测(Python)

时间序列数据与其它数据不同主要是因为时间序列数据在时间维度上存在依赖关系&#xff0c;这说明在时间序列数据当中过去的历史数据当中隐藏着一些时间序列数据固有的特性&#xff0c;例如&#xff0c;周期性、趋势性、不规则性等。时间序列预测便是通过不同的方法来捕捉这种规…

【抽象工厂模式】从理论到实战:构建可扩展的软件家族(设计模式系列)

文章目录 Java设计模式系列&#xff1a;抽象工厂模式详解1. 引言抽象工厂模式概述为何选择抽象工厂模式 2. 基础知识回顾Java基础概念复习面向对象编程原则设计模式的原则和目的 3. 抽象工厂模式的定义定义与解释模式的目的与其他工厂模式的区别 4. 抽象工厂模式的结构抽象产品…

【Android】数据持久化——数据存储

持久化技术简介 在你打开完成了一份PPT之后关闭程序&#xff0c;再次打开肯定是希望之前的内容还存在在电脑上&#xff0c;一打开PPT&#xff0c;之前的内容就自动出现了。数据持久化就是将那些内存中的瞬时数据保存到存储设备中&#xff0c;保证即使在手机或电脑关机的情况下…