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

2024-02-14 23:20

本文主要是介绍Java并发基础: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全面解析!

这篇关于Java并发基础:ConcurrentLinkedDeque全面解析!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/709861

相关文章

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("