面试系列 - Java 并发容器详解
Java 并发容器是一组用于在多线程环境下安全访问和操作数据的数据结构。它们提供了线程安全的集合和映射,使开发者能够更轻松地处理并发编程问题。
一、Java并发容器
-
ConcurrentHashMap: 它是一个高效的并发哈希表,支持多线程并发操作而不需要显式的同步。适用于高并发读和写操作的场景。
-
CopyOnWriteArrayList :这个容器支持在迭代期间进行并发写操作。当需要在读操作频繁而写操作较少的场景中使用。
-
ConcurrentLinkedQueue 和 ConcurrentLinkedDeque: 这些是非阻塞队列实现,适用于高吞吐量的并发队列操作。通常用于任务调度、工作队列等场景。
-
BlockingQueue: 它是一组阻塞队列的接口,如 LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue 等。这些队列提供了在队列为空或满时阻塞线程的能力,适用于生产者-消费者问题和任务调度等。
-
Semaphore 和 CountDownLatch: 虽然不是容器,但它们是并发编程中常用的同步工具。
二、ConcurrentHashMap
ConcurrentHashMap
是 Java 中的一个线程安全的哈希表(散列表)实现,它允许多个线程同时读取数据,而不需要显式的同步操作,并且支持高并发的读和写操作。ConcurrentHashMap
的主要特点包括:
-
并发读取: 多个线程可以同时读取数据,不会被阻塞,这使得
ConcurrentHashMap
在读多写少的场景下非常高效。 -
分段锁:
ConcurrentHashMap
内部使用了分段锁机制,将数据分成多个段(Segment),每个段上都有一个锁,不同线程可以同时访问不同段的数据,从而提高并发性能。 -
线程安全写入: 写操作(如插入、更新、删除)仍然是线程安全的,但它不会锁住整个数据结构,而只锁住相关的段,使得其他线程仍然可以访问不同段的数据。
-
弱一致性:
ConcurrentHashMap
提供了一定程度的弱一致性,因为它不保证在某一时刻数据的完全一致性,但保证了线程的安全性。 -
扩展性:
ConcurrentHashMap
具有较好的扩展性,可以通过调整初始容量和负载因子来优化性能。 -
迭代支持:
ConcurrentHashMap
提供了安全的迭代器,它可以在遍历时支持并发写操作。
ConcurrentHashMap用法示例
import java.util.concurrent.ConcurrentHashMap;public class ConcurrentHashMapExample {public static void main(String[] args) {ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();// 插入数据map.put("one", 1);map.put("two", 2);map.put("three", 3);// 获取数据int value = map.get("two");System.out.println("Value for key 'two': " + value);// 删除数据map.remove("three");// 迭代数据for (String key : map.keySet()) {int val = map.get(key);System.out.println(key + ": " + val);}}
}
虽然 ConcurrentHashMap
支持高并发读取和写入,但在一些特定情况下仍然需要考虑同步和竞态条件问题。
三、CopyOnWriteArrayList
CopyOnWriteArrayList
是 Java 集合框架中的一种线程安全的并发容器,它提供了一种特殊的并发策略,适用于读多写少的场景。与普通的 ArrayList
不同,CopyOnWriteArrayList
在写操作时不对原有数据进行修改,而是创建一个新的副本,这样可以避免读操作与写操作之间的竞态条件,保证读操作的线程安全。
主要特点和用途包括:
-
线程安全的读取: 多个线程可以同时读取数据,而不需要显式的同步操作。这使得
CopyOnWriteArrayList
在读多写少的场景下非常高效。 -
写入时复制(Copy-On-Write): 写入操作会创建一个新的副本,而不是修改原有数据。这确保了写操作不会影响正在进行的读操作,保证了读操作的一致性。原有的数据在写入完成后会被废弃。
-
高效的迭代:
CopyOnWriteArrayList
提供了安全的迭代器,可以在迭代集合时进行并发写操作,不会抛出ConcurrentModificationException
异常。 -
适用于不频繁写入的场景: 由于写操作涉及复制数据,因此
CopyOnWriteArrayList
适用于写操作不频繁、但读操作较多的情况,如事件监听器列表、观察者模式等。
下面是一个简单示例,展示了如何使用 CopyOnWriteArrayList
:
import java.util.concurrent.CopyOnWriteArrayList;public class CopyOnWriteArrayListExample {public static void main(String[] args) {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();// 插入数据list.add("one");list.add("two");list.add("three");// 获取数据String value = list.get(1);System.out.println("Value at index 1: " + value);// 迭代数据for (String item : list) {System.out.println(item);}// 写操作不影响正在进行的读操作list.add("four");// 迭代数据(包含新添加的元素)for (String item : list) {System.out.println(item);}}
}
四、BlockingQueue
BlockingQueue
是 Java 并发编程中的一个重要接口,它表示一种线程安全的阻塞队列。阻塞队列在多线程编程中非常有用,因为它提供了一种线程安全的方式来实现生产者-消费者模型和其他协作模式。
BlockingQueue
接口继承自 Queue
接口,它定义了一组用于插入、删除和检查元素的方法,但与普通的队列不同,BlockingQueue
的方法具有阻塞特性,这意味着当队列为空时,从队列中取元素的操作会被阻塞,直到有元素可用;当队列已满时,向队列中插入元素的操作也会被阻塞,直到有空间可用。
常见的 BlockingQueue
实现包括:
-
LinkedBlockingQueue: 基于链表的阻塞队列,可以设置容量,当队列为空或已满时,操作会被阻塞。
-
ArrayBlockingQueue: 基于数组的阻塞队列,需要指定容量,当队列为空或已满时,操作会被阻塞。
-
PriorityBlockingQueue: 基于优先级的阻塞队列,元素会按照优先级进行排序,不需要容量限制。
-
DelayQueue: 用于存放实现了
Delayed
接口的元素,元素只能在延迟时间过后才能被取出。 -
LinkedTransferQueue: 支持多个生产者和消费者的队列,具有高吞吐量。
-
SynchronousQueue: 一种特殊的阻塞队列,它没有容量,用于直接传递数据的通信。
使用 BlockingQueue
可以轻松实现一些经典的多线程协作模式,例如生产者-消费者模型。以下是一个简单示例,演示了如何使用 LinkedBlockingQueue
实现生产者-消费者模型:
import java.util.concurrent.*;public class ProducerConsumerExample {public static void main(String[] args) {BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(10);// 生产者线程Thread producer = new Thread(() -> {try {for (int i = 1; i <= 10; i++) {queue.put(i);System.out.println("Produced: " + i);Thread.sleep(100);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 消费者线程Thread consumer = new Thread(() -> {try {for (int i = 1; i <= 10; i++) {int value = queue.take();System.out.println("Consumed: " + value);Thread.sleep(200);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});producer.start();consumer.start();}
}
在上述示例中,生产者线程不断往队列中放入数据,而消费者线程则不断从队列中取出数据,它们通过阻塞队列实现了线程间的协作。这种模型可以用于处理多个生产者和消费者的情况,并确保线程安全。
BlockingQueue
是并发编程中的一个重要工具,可以简化线程之间的通信和同步,减少了开发者需要处理的低级细节。根据具体的需求,选择合适的 BlockingQueue
实现非常重要。
五、Semaphore
Semaphore
是 Java 并发编程中的一个同步工具,用于控制同时访问某个资源的线程数量。它允许多个线程并发访问共享资源,但可以限制同时访问的线程数目,从而实现资源的有序分配。
Semaphore
主要有两个重要的操作:
-
acquire(): 当一个线程希望获取资源时,它调用
acquire()
方法。如果有可用的资源,线程将获得许可,并继续执行。如果没有可用的资源,线程将被阻塞,直到有资源可用为止。 -
release(): 当一个线程使用完资源时,它调用
release()
方法来释放资源。这会导致信号量的许可数增加,从而允许其他等待资源的线程获得许可并继续执行。
Semaphore
可以用于以下一些典型的场景:
-
控制资源访问数量: 限制同时访问某个共享资源的线程数量,例如数据库连接池的控制。
-
实现有界容器: 可以用
Semaphore
来实现有界的集合,例如有界队列。 -
实现并发任务控制: 控制并发任务的数量,例如控制同时运行的线程数量。
下面是一个简单示例,演示了如何使用 Semaphore
来控制同时执行的线程数量:
import java.util.concurrent.Semaphore;public class SemaphoreExample {public static void main(String[] args) {// 创建一个Semaphore,限制最多同时有两个线程访问共享资源Semaphore semaphore = new Semaphore(2);// 创建多个线程for (int i = 1; i <= 5; i++) {Thread thread = new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + " is waiting for a permit.");semaphore.acquire();System.out.println(Thread.currentThread().getName() + " has acquired a permit.");Thread.sleep(2000); // 模拟线程在共享资源上的操作} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {System.out.println(Thread.currentThread().getName() + " is releasing a permit.");semaphore.release();}});thread.start();}}
}
在上述示例中,我们创建了一个 Semaphore
,并限制最多同时有两个线程可以获得许可。多个线程尝试获取许可,但只有两个线程能够同时执行,其余的线程会被阻塞,直到有许可可用。
Semaphore
是一个非常有用的同步工具,可用于管理共享资源的并发访问,控制线程的并发数量,以及其他需要有序控制的场景。
六、CountDownLatch
CountDownLatch
是 Java 并发编程中的一个同步工具类,它用于实现一种等待多个线程完成任务的场景。CountDownLatch
通过一个计数器来实现,计数器的值初始化为一个正整数,然后多个线程执行任务,每个任务执行完毕后,计数器的值减一,当计数器的值变为零时,等待的线程可以继续执行。
CountDownLatch
主要有两个重要的方法:
-
countDown()
: 每个任务执行完毕后调用这个方法来减小计数器的值。通常在任务的最后一步调用。 -
await()
: 等待计数器的值变为零,一直阻塞当前线程,直到计数器值为零才继续执行。
CountDownLatch
可以用于以下一些典型的场景:
-
多个线程协作完成任务: 主线程等待多个子线程完成任务后再继续执行。
-
控制并发任务的开始: 多个线程等待某个共同的事件发生后同时开始执行。
-
统计多个线程的执行时间: 可以用于测量多个线程完成任务所花费的总时间。
下面是一个简单示例,演示了如何使用 CountDownLatch
来等待多个线程完成任务:
import java.util.concurrent.CountDownLatch;public class CountDownLatchExample {public static void main(String[] args) throws InterruptedException {int numThreads = 3;CountDownLatch latch = new CountDownLatch(numThreads);for (int i = 0; i < numThreads; i++) {Thread thread = new Thread(() -> {try {// 模拟线程执行任务Thread.sleep(2000);System.out.println(Thread.currentThread().getName() + " has completed.");} catch (InterruptedException e) {Thread.currentThread().interrupt();} finally {latch.countDown(); // 任务执行完毕,减少计数器}});thread.start();}// 等待所有线程完成latch.await();System.out.println("All threads have completed.");}
}
在上述示例中,我们创建了一个 CountDownLatch
,初始化计数器值为 3(表示有三个线程需要等待)。然后创建了三个线程,每个线程执行完毕后调用 countDown()
来减小计数器的值。主线程使用 await()
来等待计数器的值变为零,一旦所有线程都执行完毕,主线程继续执行。
CountDownLatch
是一个常用的同步工具,用于协调多个线程的执行,实现任务的并发控制。
相关文章:
面试系列 - Java 并发容器详解
Java 并发容器是一组用于在多线程环境下安全访问和操作数据的数据结构。它们提供了线程安全的集合和映射,使开发者能够更轻松地处理并发编程问题。 一、Java并发容器 ConcurrentHashMap: 它是一个高效的并发哈希表,支持多线程并发操作而不需…...

使用动态住宅代理还能带来哪些好处?
一、什么是动态住宅代理ip 动态住宅代理是一种代理技术,它利用代理服务器中转用户和目标服务器之间的网络流量,实现用户真实位置的屏蔽。代理提供商会有自己的ip大池子,当你通过代理服务器向网站发送请求时,服务器会从池子中选中…...
笙默考试管理系统-MyExamTest----codemirror(18)
笙默考试管理系统-MyExamTest----codemirror(18) 目录 一、 笙默考试管理系统-MyExamTest----codemirror 二、 笙默考试管理系统-MyExamTest----codemirror 三、 笙默考试管理系统-MyExamTest----codemirror 四、 笙默考试管理系统-MyExamTest---…...

TGA格式文件转材质
今天淘宝上买了一个美女的模型,是blender的源文件,上面说有fbx格式的。我用unity,所以觉得应该可以用。文件内容如下图: FBX文件夹打开后,内容如下图所示,当时就预感到可能没有色彩。 unity打开后果然发现只…...

IP应用场景查询API:深入了解网络用户行为的利器
前言 随着数字时代的不断发展,互联网已经成为人们生活的重要组成部分。而随着越来越多的业务和社交活动迁移到在线平台上,了解和理解网络用户行为变得至关重要。为了满足这个需求,IP 应用场景查询 API 崭露头角,成为深入了解网络…...

docker从零部署jenkins保姆级教程(上)
jenkins,基本是最常用的持续集成工具。在实际的工作中,后端研发一般没有jenkins的操作权限,只有一些查看权限,但是我们的代码是经过这个工具构建出来部署到服务器的,所以我觉着有必要了解一下这个工具的搭建过程以及简…...

2023数模A题——定日镜场的优化问题
A题——定日镜场的优化问题 思路:该题主要考察的几何知识和天文学知识,需要不同角度下的镜面和遮挡情况。 资料获取 问题1: 若将吸收塔建于该圆形定日镜场中心,定日镜尺寸均为 6 m6 m,安装高度均为 4 m,且…...
Container is running beyond memory limits
问题 Hadoop环境中,执行MapReduce程序或者Hive 任务时候,任务执行失败,提示内存不足。 Container is running 337869312B beyond the VIRTUAL’ memory limit.Current usage:295.8 NB of 1 GB physical memoryused;2.4 GB of 2.1 GB virtual…...

Java后端开发面试题——JVM虚拟机篇
目录 什么是程序计数器? 你能给我详细的介绍Java堆吗? 什么是虚拟机栈 1. 垃圾回收是否涉及栈内存? 2. 栈内存分配越大越好吗? 3. 方法内的局部变量是否线程安全? 4.什么情况下会导致栈内存溢出? 5.堆栈的区别…...

SpringMVC增删改查(CRUD)的实现
目录 前言 一、前期准备 1.pom.xml---依赖与插件的导入 2.jdbc.properties---数据库连接 3.log4j2.xml---日志文件 4.spring-mybatis 5.spring-context 6.spring-mvc 二、增删改查的实现 1.model与mapper层的生成 2.biz层 3.工具类 4.controller层 三、测试结果 总…...

智安网络|面临日益增长的安全威胁:云安全和零信任架构的重要性
随着云计算技术的快速发展和广泛应用,云安全和零信任架构变得愈发重要。在数字化时代,云计算技术得到了广泛的应用和推广。企业和组织借助云服务提供商的强大能力,实现了高效、灵活和可扩展的IT基础设施。然而,随着云环境的快速发…...
JVM常用调优策略
1、JVM调优的核心关注指标 调优之前首先我们要知道怎样才算是“优”,不能笼统的说我的程序性能很好,所以就需要有一个具体的指标来衡量性能情况,而在JVM里面衡量性能两个指标分别“吞吐量”和“停顿时间”。 吞吐量:程序运行过程…...
自动化防火墙放行目标域名IP
#!/bin/bash # 设置要获取IP地址的域名 domain"yourdomain.com"# 获取域名的IP地址 new_ip$(dig short A $domain)# 移除之前添加放行的IP地址(通过备注找它的编号) rule_number$(iptables -L INPUT -n --line-numbers -v | awk -v domain&quo…...
12.2RAC环境从RAC转为单机模式的问题处理
近期,在某用户的测试环境,需要将RAC转为单机模式,然后进行数据恢复;一开始只是将数据库软件通过make -f ins_rdbms.mk rac_off 和 make -f ins_rdbms.mk ioracle关闭RAC模式;然后在启动数据库(sqlplus / a…...
Docker 中 jdk8容器里无法使用 JDK 的 jmap 等命令的问题
一、问题描述 项目部署在 CentOS 服务器上。项目偶尔会出现无响应的情况,这时理所当然要上去用 JDK 相关命令看看堆栈和GC等信息了。 进入 Java 程序所在容器:docekr-compose exec api bash,进入到 api 容器的 bash 终端。 jps 打印 Java …...
typeScript--[es6class类实现继承]
一.js中实现继承 // js实现继承 // 父类 function Father(name) {this.name namethis.say function () {console.log(this.name "在唱歌")} } var f new Father("逍遥的码农")// 子类 function Son(name) {Father.call(this, name) } Son.prototype …...

解决eclipse的报错:Must declare a named package because this compilation
刚安装完成eclipse, 创建类的时候报错 报错信息如下: 原因:新版本的ECLIPSE要求每一个类都必须定义在包里面 解决方法:创建类的时候指定类的名字,如下图:Package 里面填写ch3,表示包名 创建完成…...
linux sed常用各种操作大全
经常使用,但有些总记不全,有时候经常查找,这次全部捋清楚做备忘,有需要的小伙伴欢迎收藏起来哦! 查、增、改、删一应俱全,非常详细! 目录 一、查看 查看第2行 查看第2行到第3行 查看第1行、…...

通过 Keycloak 结合 OAuth2.0协议进行 Amazon API Gateway 鉴权
1. 简介 本文介绍了如何通过 Keycloak,并结合 Amazon API Gateway 内置的授权功能,完成对 Amazon 资源请求的鉴权过程。API Gateway 帮助开发者安全的的创建、发布、维护并管理 API 的访问。在中国区,由于Cognito 仍未上线,因此使…...

修复中间件log4j漏洞方案(直接更换漏洞jar包)
说明: 后台服务里面的log4j漏洞我们已经全部升级处理了,但是一些中间件镜像包里的log4j漏洞需要单独处理 解决办法以ElasticSearch7.6.2为例: 方法: (1)找到容器里面有哪些旧的log4j依赖包 (…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...

HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...

iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...