Java并发基础:ConcurrentLinkedDeque全面解析!

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: ConcurrentLinkedDeque类提供了线程安全的双端队列操作,支持高效的并发访问,因此在多线程环境下,可以放心地在队列的两端添加或移除元素,而不用担心数据的一致性问题。同时,它的内部实现采用了无锁算法,从而避免了传统锁带来的性能开销。

Java并发基础:ConcurrentLinkedDeque全面解析! - 程序员古德

内容概要

ConcurrentLinkedDeque类提供了线程安全的双端队列操作,支持高效的并发访问,因此在多线程环境下,可以放心地在队列的两端添加或移除元素,而不用担心数据的一致性问题。同时,它的内部实现采用了无锁算法,从而避免了传统锁带来的性能开销。

核心概念

假如,有一个在线聊天应用,该应用允许多个用户同时在线聊天,它们可以创建不同的聊天室,并在这些聊天室里发送和接收消息,为了处理和存储这些消息,因此需要一个数据结构来存储和管理它们。

可以将每个聊天室看作是一个ConcurrentLinkedDeque实例,其中的每个元素都是一条消息,由于ConcurrentLinkedDeque是线程安全的,这意味着多个线程可以同时向同一个聊天室添加或删除消息,而不会导致数据混乱或不一致。

当用户发送一条消息时,可以将这条消息添加到相应聊天室的ConcurrentLinkedDeque的尾部,而当用户查看聊天室的消息历史时,可以从ConcurrentLinkedDeque的头部开始遍历并显示消息,由于ConcurrentLinkedDeque支持在两端进行高效的操作,因此这种使用场景非常合适。ConcurrentLinkedDeque还提供了更加安全的并发操作方法,如offerFirstofferLastpollFirstpollLast等,这些方法可以在多线程环境下安全地添加和删除元素。

ConcurrentLinkedDeque实现了Deque接口,并提供了一个线程安全的双端队列,这个数据结构被设计用来解决多线程环境下的并发问题,特别是在需要线程安全地从队列的两端添加或者移除元素时,它通常适合处理如下类似的场景的问题:

  1. 线程安全:在多线程环境下,普通的集合类(如ArrayList, LinkedList)不是线程安全的,如果多个线程同时修改这些集合,可能会导致数据不一致或者其他未定义的行为,ConcurrentLinkedDeque通过内部的并发控制机制,确保了多个线程可以安全地同时访问和修改队列。
  2. 高并发性能:与SynchronizedDeque相比,ConcurrentLinkedDeque通过无锁(lock-free)或者最小化锁竞争的设计,提供了更高的吞吐量,它在内部使用了一种称为CAS(Compare-and-Swap)的原子操作来实现无锁算法,从而减少了线程间的竞争和阻塞。
  3. 双端操作:与只能在一端添加或移除元素的队列(如BlockingQueue接口的实现类)不同,ConcurrentLinkedDeque支持在队列的两端进行高效的添加和移除操作,这使得它在某些算法和数据结构中特别有用,比如实现一个线程安全的LRU缓存
  4. 内存一致性ConcurrentLinkedDeque保证了内存一致性效应,即一个线程对队列的修改对其他线程是立即可见的(遵循happens-before关系),这是通过内部的volatile变量和适当的同步机制来实现的。

代码案例

以下是一个简单的代码示例,演示了如何使用ConcurrentLinkedDeque类,该示例模拟了一个多线程环境中的生产者-消费者场景,其中生产者线程向队列中添加元素,而消费者线程从队列中移除并处理元素,如下代码:

import java.util.concurrent.ConcurrentLinkedDeque;  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.TimeUnit;  

public class ConcurrentLinkedDequeExample {
   
     

    // 创建一个ConcurrentLinkedDeque实例  
    private static final ConcurrentLinkedDeque<Integer> deque = new ConcurrentLinkedDeque<>();  

    // 生产者任务:向队列中添加元素  
    private static class Producer implements Runnable {
   
     
        @Override  
        public void run() {
   
     
            int producedCount = 0;  
            while (producedCount < 10) {
   
    // 生产10个元素后停止  
                deque.offerLast(producedCount++); // 在队列尾部添加元素  
                System.out.println("Produced: " + (producedCount - 1));  
                try {
   
     
                    // 稍微延迟一下,模拟生产过程  
                    Thread.sleep(100);  
                } catch (InterruptedException e) {
   
     
                    Thread.currentThread().interrupt();  
                    return;  
                }  
            }  
        }  
    }  

    // 消费者任务:从队列中移除并处理元素  
    private static class Consumer implements Runnable {
   
     
        @Override  
        public void run() {
   
     
            while (true) {
   
     
                Integer consumed = deque.pollFirst(); // 尝试从队列头部移除元素  
                if (consumed == null) {
   
     
                    // 队列为空,退出循环(在实际应用中可能需要更复杂的退出条件)  
                    break;  
                }  
                System.out.println("Consumed: " + consumed);  
                try {
   
     
                    // 稍微延迟一下,模拟消费过程  
                    Thread.sleep(150);  
                } catch (InterruptedException e) {
   
     
                    Thread.currentThread().interrupt();  
                    return;  
                }  
            }  
        }  
    }  

    public static void main(String[] args) throws InterruptedException {
   
     
        // 创建一个固定大小的线程池  
        ExecutorService executorService = Executors.newFixedThreadPool(3);  

        // 提交一个生产者任务到线程池  
        executorService.submit(new Producer());  

        // 提交两个消费者任务到线程池  
        executorService.submit(new Consumer());  
        executorService.submit(new Consumer());  

        // 让主线程等待一段时间,以便生产者和消费者任务可以执行完成  
        // 注意:在实际应用中,应该使用更可靠的机制来等待任务的完成,比如Future.get()或CountDownLatch等。  
        TimeUnit.SECONDS.sleep(5);  

        // 关闭线程池(这会导致正在执行的任务被中断,因此在实际应用中需要谨慎处理)  
        executorService.shutdownNow();  
        // 等待线程池终止(这里是为了示例代码的完整性,实际应用中可能需要根据具体情况来处理)  
        executorService.awaitTermination(1, TimeUnit.MINUTES);  
    }  
}

核心API

下面是一些 ConcurrentLinkedDeque 中常用的方法及其含义:

1、添加元素

  1. offerFirst(E e): 将指定的元素插入此双端队列的开头,如果成功则返回 true,如果当前没有可用空间则返回 false
  2. offerLast(E e): 将指定的元素插入此双端队列的末尾,如果成功则返回 true,如果当前没有可用空间则返回 false
  3. addFirst(E e), addLast(E e): 类似于 offerFirstofferLast,但是如果添加失败会抛出 IllegalStateException
  4. push(E e): 将元素推入此双端队列所表示的堆栈(如果可能的话),等效于 addFirst

2、移除元素

  1. pollFirst(): 获取并移除此双端队列的第一个元素,或返回 null 如果此双端队列为空。
  2. pollLast(): 获取并移除此双端队列的最后一个元素,或返回 null 如果此双端队列为空。
  3. removeFirst(), removeLast(): 类似于 pollFirstpollLast,但是如果双端队列为空会抛出 NoSuchElementException
  4. pop(): 从此双端队列所表示的堆栈中弹出一个元素,等效于 removeFirst

3、检查元素

  1. peekFirst(): 获取但不移除此双端队列的第一个元素,或返回 null 如果此双端队列为空。
  2. peekLast(): 获取但不移除此双端队列的最后一个元素,或返回 null 如果此双端队列为空。

4、其他方法

  1. isEmpty(): 如果此双端队列不包含任何元素,则返回 true
  2. size(): 返回此双端队列中的元素数量。注意,由于并发修改,返回的数量可能只是近似值。
  3. iterator(): 返回在此双端队列的元素上进行迭代的迭代器。返回的迭代器是弱一致性的。
  4. descendingIterator(): 返回在此双端队列的元素上进行逆序迭代的迭代器。返回的迭代器是弱一致性的。

5、并发控制相关

  1. 由于 ConcurrentLinkedDeque 的设计是为了支持高并发,因此它内部的实现使用了复杂的非阻塞算法和原子操作来确保线程安全。
  2. 与此同时,ConcurrentLinkedDeque 的迭代器是弱一致性的,这意味着它们能够反映出它们被构造时原始集合的某个状态,但是如果集合在迭代过程中被并发修改,迭代器不一定能够反映这些修改。

核心总结

Java并发基础:PriorityBlockingQueue全面解析! - 程序员古德

ConcurrentLinkedDeque类是一个高效、线程安全的双端队列,有着出色的并发性能,能够在多线程环境下保持较高的吞吐量,且支持在队列两端进行快速的插入和删除操作,采用无锁算法,使它避免了传统锁机制带来的性能开销和死锁风险。在高并发场景下,由于无锁算法的复杂性,可能会导致较高的CPU占用率,此外,其size()方法返回的元素数量是近似值,不适合需要精确计数的场景

关注我,每天学习互联网编程技术 - 程序员古德

END!
END!
END!

往期回顾

精品文章

Java并发基础:PriorityBlockingQueue全面解析!

Java并发基础:DelayQueue全面解析!

Java并发基础:LinkedBlockingDeque全面解析!

Java并发基础:LinkedTransferQueue全面解析!

Java并发基础:LinkedBlockingQueue全面解析!

精彩视频

相关文章
|
9天前
|
传感器 监控 Java
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
34 5
|
21天前
|
Java API 数据处理
深潜数据海洋:Java文件读写全面解析与实战指南
通过本文的详细解析与实战示例,您可以系统地掌握Java中各种文件读写操作,从基本的读写到高效的NIO操作,再到文件复制、移动和删除。希望这些内容能够帮助您在实际项目中处理文件数据,提高开发效率和代码质量。
27 4
|
1月前
|
XML JSON Java
Java中Log级别和解析
日志级别定义了日志信息的重要程度,从低到高依次为:TRACE(详细调试)、DEBUG(开发调试)、INFO(一般信息)、WARN(潜在问题)、ERROR(错误信息)和FATAL(严重错误)。开发人员可根据需要设置不同的日志级别,以控制日志输出量,避免影响性能或干扰问题排查。日志框架如Log4j 2由Logger、Appender和Layout组成,通过配置文件指定日志级别、输出目标和格式。
|
2月前
|
存储 Java 计算机视觉
Java二维数组的使用技巧与实例解析
本文详细介绍了Java中二维数组的使用方法
62 15
|
16天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
142 60
【Java并发】【线程池】带你从0-1入门线程池
|
5天前
|
存储 网络协议 安全
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
55 23
|
12天前
|
Java 调度
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
81 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
|
27天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
105 14
|
1月前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
57 13
|
1月前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。

推荐镜像

更多