【不安全的集合类】同步容器(如ConcurrentHashMap)、并发集合(如CopyOnWriteArrayList)

本文主要是介绍【不安全的集合类】同步容器(如ConcurrentHashMap)、并发集合(如CopyOnWriteArrayList),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 一、List的线程不安全
    • 二、Set的线程不安全
    • 三、Map的线程不安全

日常我们用到的集合的情况会很多,在单线程的情况下,不用考虑到线程安全的问题,但是如果在多线程开发的过程中,我们该选择哪一种类型来保证线程安全性呢

一、List的线程不安全

我们先来看一个例子:

package com.atguigu.signcenter.nosafe;import java.util.ArrayList;
import java.util.UUID;/*** 题目:请举例说明集合类是不安全的* @author: jd* @create: 2024-09-02*/
public class NotSafeDemo {public static void main(String[] args) {ArrayList<String> StrList = new ArrayList<>();for (int i = 0; i <=30 ; i++) {new Thread(()->{StrList.add(UUID.randomUUID().toString().substring(0,8));System.out.println("StrList = " + StrList);},String.valueOf(i)).start();}}}

结果:从图中可以看出来,执行一段时间之后发生了错误;这个错误就是多线程任务中对同一个集合处理过程中出现了冲突的情况导致的
在这里插入图片描述
导致原因&解决方案
会出现ConcurrentModificationException是因为ArrayList的add方法不是线程安全的;当某个线程正在向List中写入数据时,另外一个线程同时进来写入,就会导致ConcurrentModificationException。

  1. 我们可以使用线程安全的集合类Vector,其add方法是同步方法(保证了数据一致性,但是访问性能下降);
  2. 使用Collections.synchronizedList(new ArrayList<>()) 创建一个线程安全的List (其实就是在add的时候使用了synchronized同步代码块);
  3. CopyOnWriteArrayList 写时复制ArrayList (多线程建议使用这个)
    Vector是线程安全的,能够保证数据一致性但是性能低;ArrayList牺牲了数据一致性提升了读写效率;现在想要保证数据一致性的同时也要保证读写效率,那应该怎么办?因此出现了读写分离的CopyOnWriteArrayList 。

CopyOnWriteArrayList的写时复制
我们可以先看看CopyOnWriteArrayList中add方法的源码

private transient volatile Object[] array;public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();   // 获取当前List中的所有元素int len = elements.length;  Object[] newElements = Arrays.copyOf(elements, len + 1);  // 拷贝旧元素到新的扩容的数组中newElements[len] = e;  // 添加新的值setArray(newElements);  // 更新return true;} finally {lock.unlock();}
}

CopyOnWrite容器即写时复制的容器,往一个容器中添加元素的时候,不直接往当前容器Object[]添加,而是现将当前容器Object[]进行Copy,而是复制出一个新的容器Object[] newElements向新容器添加元素,添加之后,再将原容器的引用指向新的容器setArray(newElements);这样做的好处时可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。同时添加元素的过程是通过ReentrantLock 锁来实现了同一时间内只有一个线程对此方法的访问。

二、Set的线程不安全

public class NotSafeDemo {public static void main(String[] args) {Set<String> set = new HashSet<>();for (int i = 1; i <= 30; i++) {new Thread(() -> {set.add(UUID.randomUUID().toString().substring(0, 8));System.out.println(set);}, String.valueOf(i)).start();}}
}

面这段代码同样会出现java.util.ConcurrentModificationException异常;同样可以使用Collections.synchronizedSet()和CopyOnWriteArraySet,其具体原理与List的一致。这里浅说一下HashSet的源码,HashSet其实就是一个HashMap,HashSet的中存的值是HashMap的key,HashMap中的Value是一个固定对象PRESENT。

三、Map的线程不安全

同样HashMap也是线程不安全的,可以使用集合工具类Collections.synchronizedMap(new HashMap<String, String>())和ConcurrentHashMap创建线程安全的HashMap

public class NotSafeDemo {public static void main(String[] args) {
//        HashMap<String, String> map = new HashMap<>();
//        HashMap<String, String> map1 = (HashMap<String, String>) Collections.synchronizedMap(new HashMap<String, String>());ConcurrentHashMap<String, String> map = new ConcurrentHashMap<String, String>();for (int i = 1; i <= 30; i++) {new Thread(() -> {map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));System.out.println(map);}, String.valueOf(i)).start();}}
}

结果正确且正常结束
在这里插入图片描述

这篇关于【不安全的集合类】同步容器(如ConcurrentHashMap)、并发集合(如CopyOnWriteArrayList)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Nginx实现高并发的项目实践

《Nginx实现高并发的项目实践》本文主要介绍了Nginx实现高并发的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录使用最新稳定版本的Nginx合理配置工作进程(workers)配置工作进程连接数(worker_co

Java集合中的List超详细讲解

《Java集合中的List超详细讲解》本文详细介绍了Java集合框架中的List接口,包括其在集合中的位置、继承体系、常用操作和代码示例,以及不同实现类(如ArrayList、LinkedList和V... 目录一,List的继承体系二,List的常用操作及代码示例1,创建List实例2,增加元素3,访问元

Java中将异步调用转为同步的五种实现方法

《Java中将异步调用转为同步的五种实现方法》本文介绍了将异步调用转为同步阻塞模式的五种方法:wait/notify、ReentrantLock+Condition、Future、CountDownL... 目录异步与同步的核心区别方法一:使用wait/notify + synchronized代码示例关键

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

C#比较两个List集合内容是否相同的几种方法

《C#比较两个List集合内容是否相同的几种方法》本文详细介绍了在C#中比较两个List集合内容是否相同的方法,包括非自定义类和自定义类的元素比较,对于非自定义类,可以使用SequenceEqual、... 目录 一、非自定义类的元素比较1. 使用 SequenceEqual 方法(顺序和内容都相等)2.

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

Spring核心思想之浅谈IoC容器与依赖倒置(DI)

《Spring核心思想之浅谈IoC容器与依赖倒置(DI)》文章介绍了Spring的IoC和DI机制,以及MyBatis的动态代理,通过注解和反射,Spring能够自动管理对象的创建和依赖注入,而MyB... 目录一、控制反转 IoC二、依赖倒置 DI1. 详细概念2. Spring 中 DI 的实现原理三、

详谈redis跟数据库的数据同步问题

《详谈redis跟数据库的数据同步问题》文章讨论了在Redis和数据库数据一致性问题上的解决方案,主要比较了先更新Redis缓存再更新数据库和先更新数据库再更新Redis缓存两种方案,文章指出,删除R... 目录一、Redis 数据库数据一致性的解决方案1.1、更新Redis缓存、删除Redis缓存的区别二

基于Redis有序集合实现滑动窗口限流的步骤

《基于Redis有序集合实现滑动窗口限流的步骤》滑动窗口算法是一种基于时间窗口的限流算法,通过动态地滑动窗口,可以动态调整限流的速率,Redis有序集合可以用来实现滑动窗口限流,本文介绍基于Redis... 滑动窗口算法是一种基于时间窗口的限流算法,它将时间划分为若干个固定大小的窗口,每个窗口内记录了该时间

Nacos集群数据同步方式

《Nacos集群数据同步方式》文章主要介绍了Nacos集群中服务注册信息的同步机制,涉及到负责节点和非负责节点之间的数据同步过程,以及DistroProtocol协议在同步中的应用... 目录引言负责节点(发起同步)DistroProtocolDistroSyncChangeTask获取同步数据getDis