Java中线程安全的集合类

在先前的文章中我们已经讲过了原子类(线程安全的基本类型,基于CAS实现),详见常见锁策略,synchronized内部原理以及CAS-CSDN博客 ,我们在来讲一下集合类,在原来的集合类,大多数是线程不安全的,虽然vector,Stack,HashTable 是线程安全的,但由于其在一些关键方法上都加了synchronized,导致同时读以及单线程中也要加锁,于是java官方已经将其标为不安全类,不建议使用了,那我们如何在多线程下保证安全的使用一些集合类。

 多线程环境下使用ArrayList

1)自己使用同步机制(synchronized或者ReentrantLock)具体内容可以看这里Java线程安全问题以及解决方案-CSDN博客

2)使用Collections.synchronizedList()

List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());

用这种方式创建出的synchronizedList在关键方法上都具有synchronized

3)使用CopyOnWriteArrayList

CopyOnWriteArrayList 是 Java 中的一种并发集合类,它提供了一种在迭代时保证线程安全的机制。它的特点是在对集合进行修改(添加、删除元素)时,不直接在原始数据上进行操作,而是先将原始数据复制一份,然后在副本上进行修改,最后再将修改后的副本替换原始数据,且同时写时也会创建出两个拷贝。这种机制保证了在迭代过程中不会发生并发修改异常(ConcurrentModificationException),因为每次迭代都是在集合的一个固定的副本上进行的。

import java.util.concurrent.CopyOnWriteArrayList;public class ConcurrentListExample {private static final int THREAD_COUNT = 3;private static final int OPERATIONS_PER_THREAD = 10000;private static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();public static void main(String[] args) {// 创建并启动多个线程进行写操作for (int i = 0; i < THREAD_COUNT; i++) {Thread writerThread = new Thread(() -> {for (int j = 0; j < OPERATIONS_PER_THREAD; j++) {list.add(j);}});writerThread.start();}// 创建并启动多个线程进行读操作for (int i = 0; i < THREAD_COUNT; i++) {Thread readerThread = new Thread(() -> {for (int j = 0; j < OPERATIONS_PER_THREAD; j++) {int size = list.size(); // 读取列表大小System.out.println("List size: " + size);try {Thread.sleep(10); // 模拟其他处理} catch (InterruptedException e) {e.printStackTrace();}}});readerThread.start();}}
}

列表大小的变化:读线程会定期读取列表的大小并打印出来。由于写线程在不断地往列表中添加元素,因此列表的大小会逐渐增加。读线程每次读取到的列表大小可能会不同,取决于它在列表大小被修改之前或之后读取到的。读线程的竞争:由于多个读线程同时进行,它们可能会竞争访问 CopyOnWriteArrayList 的大小。因此输出结果中可能会出现多个读线程同时读取列表大小并打印的情况。写线程的竞争:多个写线程同时向 CopyOnWriteArrayList 中添加元素,它们之间也可能存在竞争。因此,列表中的元素可能会以不确定的顺序被添加。

如何安全地使用 CopyOnWriteArrayList

  1. 并发读写安全CopyOnWriteArrayList 在迭代时提供了线程安全的保证,因此可以安全地在多个线程中进行读操作和写操作,而无需额外的同步控制。

  2. 适用场景:适用于读操作远远多于写操作的场景,因为每次写操作都会触发一次数组的拷贝,可能会带来一定的性能开销。

  3. 实时性:需要注意,由于写操作会对原始数据进行拷贝,因此在写操作完成之前,迭代器可能会遍历到老的数据。如果需要实时性较高的结果,可能需要其他方式进行处理。

  4. 性能考虑:虽然 CopyOnWriteArrayList 提供了线程安全的迭代机制,但在写操作频繁的情况下,可能会产生较高的开销,因为每次写操作都需要拷贝整个数组。因此,对于写操作频繁的场景,可能需要考虑其他更合适的并发集合类。

总的来说,CopyOnWriteArrayList 是一种适用于读多写少的场景下保证线程安全的并发集合类,能够有效地解决在多线程环境中的并发访问问题。

多线程环境使用哈希表

HashMap 本身不是线程安全的,我们可以使用Hashtable, ConcurrentHashMap

Hashtable

  1. 线程安全性

    • Hashtable 是线程安全的,所有的公共方法都是同步的,因此可以在多线程环境中安全地使用。
  2. 性能

    • Hashtable 的所有方法都是同步的,而且是对整个HashTable加锁,这意味着在高并发的情况下可能会出现性能瓶颈。因为每次操作都需要获得锁来保证线程安全性。
    • Hashtable 使用一个称为扩容阈值(Expansion Threshold)的参数来控制何时需要进行扩容。当添加元素时,如果元素数量超过了当前容量乘以加载因子(Load Factor),就会触发扩容操作。默认情况下,加载因子是 0.75。扩容操作会创建一个新的数组,大小是原数组的两倍加一,然后将原有数据重新哈希到新数组中。因为 Hashtable 的所有方法都是同步的,所以在进行扩容时会锁定整个哈希表,可能会引起性能不稳定。

ConcurrentHashMap

  1. 线程安全性
    • ConcurrentHashMap 通过使用分段锁(Segment Locking)来实现线程安全性,它将整个存储空间分成多个段(Segment),每个段拥有自己的锁,这里的段在Java8以后可以简单理解成HashAMap的每一个链表或者树,锁对象则是链表头节点或者树的根节点,因此可以在大部分情况下实现更高的并发度。在Java 8及以后的版本中,ConcurrentHashMap 使用了 CAS (Compare and Swap) 操作来进一步提高性能,比如在size()方法上使用CAS实现了轻量级锁,避免了重量级锁的出现。
  2. 性能
    • 由于 ConcurrentHashMap 使用了分段锁和 CAS 操作,因此在高并发的情况下,通常比 Hashtable 的性能要好。它提供了更好的并发性能,更适合大规模并发访问的场景。
    • 在添加元素时,ConcurrentHashMap 在初始化时会创建一定数量的段,每个段内部都是一个独立的哈希表。当添加元素时,只会锁定对应段的锁,其他段的数据仍然可以被并发访问。
    • 扩容操作会在添加元素时自动触发,并且是分段进行的。每个段在达到一定的容量阈值时会触发扩容操作,而不是等待整个哈希表的容量达到阈值。
    • ConcurrentHashMap 的扩容是分段进行的,因此不会阻塞整个哈希表,可以在一定程度上提高并发性能。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java练习(第5天)【总结】在字符串中寻找特定的字符(5种方法)

问题描述&#xff1a;在字符串中寻找特定字符 1、第1次出现位置 实现函数原型&#xff1a; int indexOf(char c) Java代码&#xff1a; import java.io.*; public class Way_1 {public static void main(String args[]){String str "Geeks for Geeks is a computer s…

深入Kafka client

分区分配策略 客户端可以自定义分区分配策略, 当然也需要考虑分区消费之后的offset提交, 是否有冲突。 消费者协调器和组协调器 a. 消费者的不同分区策略, 消费者之间的负载均衡(新消费者加入或者存量消费者退出), 需要broker做必要的协调。 b. Kafka按照消费组管理消费者, …

每日一题——LeetCode1566.重复至少K次且长度为M的模式

方法一 暴力枚举 var containsPattern function(arr, m, k) {const n arr.length;for (let l 0; l < n - m * k; l) {let offset;for (offset 0; offset < m * k; offset) {if (arr[l offset] ! arr[l offset % m]) {break;}}if (offset m * k) {return true;}}r…

【数据结构与算法】整数二分

问题描述 对一个排好序的数组&#xff0c;要求找到大于等于7的最小位置和小于等于7的最大位置 大于等于7的最小位置 易知从某个点开始到最右边的边界都满足条件&#xff0c;我们要找到这个区域的最左边的点。 开始二分&#xff01; left指针指向最左边界&#xff0c;right…

【JGit 】一个完整的使用案例

需求 生成一系列结构相同的项目代码&#xff0c;将这些项目的代码推送至一个指定的 Git 仓库&#xff0c;每个项目独占一个分支。 推送时若仓库不存在&#xff0c;则自动创建仓库。 分析 生成代码使用 Java 程序模拟&#xff0c;每个项目中模拟三个文件。Project.cpp 、Pro…

C习题002:澡堂洗澡

问题 输入样例 在这里给出一组输入。例如&#xff1a; 2 5 1 3 3 2 3 3 输出样例 在这里给出相应的输出。例如&#xff1a; No代码长度限制 16 KB 时间限制 400 ms 内存限制 64 MB 栈限制 8192 KB 代码 #include<stdio.h> int main() {int N,W,s,t,p;int arr_s[…

将任何网页变成桌面应用,全平台支持 | 开源日报 No.184

tw93/Pake Stars: 20.9k License: MIT Pake 是利用 Rust 轻松构建轻量级多端桌面应用的工具。 与 Electron 包大小相比几乎小了 20 倍&#xff08;约 5M&#xff01;&#xff09;使用 Rust Tauri&#xff0c;Pake 比基于 JS 的框架更轻量和更快内置功能包括快捷方式传递、沉浸…

测试需求平台9-Table 组件应用产品列表优化

✍此系列为整理分享已完结入门搭建《TPM提测平台》系列的迭代版&#xff0c;拥抱Vue3.0将前端框架替换成字节最新开源的arco.design&#xff0c;其中约60%重构和20%新增内容&#xff0c;定位为从 0-1手把手实现简单的测试平台开发教程&#xff0c;内容将囊括基础、扩展和实战&a…

(每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(一)

项目建议与立项申请、初步可行性研究、详细可行性研究、评估与决策是项目投资前使其的四个阶段。在实际工作中&#xff0c;初步可行性研究和详细可行性研究可以依据项目的规模和繁简程度合二为一&#xff0c;但详细可行性研究是不可缺少的。升级改造项目制作初步和详细研究&…

2024年十大前景职业揭晓!提前布局,抢占未来职场制高点!

随着科技的飞速发展和社会的不断进步&#xff0c;各行各业都在经历着翻天覆地的变化。2024年即将到来&#xff0c;哪些职业将会成为未来的热门选择呢&#xff1f;本文将为您揭晓2024年十大前景职业&#xff0c;助您提前布局&#xff0c;抢占未来职场制高点&#xff01; 一、人…

Covalent Network(CQT)将链下收入引入链上,在全新阶段开启 Token 回购

Covalent Network&#xff08;CQT&#xff09;&#xff0c;是 Web3 领域跨越 225 个链的领先数据索引服务商&#xff0c;通过统一 API 的方式提供结构化数据可用性服务&#xff0c;并正在成为 AI、DeFi、分析和治理等多样化需求的关键参与者。为了支持去中心化技术的采用&#…

分布式系统中常用的缓存方案

1. 引言 随着互联网应用的发展和规模的不断扩大&#xff0c;分布式系统中的缓存成为了提升性能和扩展性的重要手段之一。本文将介绍几种在分布式系统中常用的缓存方案&#xff0c;包括分布式内存缓存、分布式键值存储、分布式对象存储和缓存网关等。 1.1 缓存在分布式系统中的…