JUC系列(三) 不安全的集合类

简介: 在多线程的情况下 我们常用的一些集合并不能保持线程的安全 那么我们该怎么办呢
📣 📣 📣 📢📢📢
☀️☀️你好啊!小伙伴,我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。
📒 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️,擅长使用Java技术开发web项目和工具
📒 文章内容丰富:覆盖大部分java必学技术栈,前端,计算机基础,容器等方面的文章
📒 如果你也对Java感兴趣,关注小冷吧,一起探索Java技术的生态与进步,一起讨论Java技术的使用与学习
✏️高质量技术专栏专栏链接: 微服务数据结构netty单点登录SSMSpringCloudAlibaba
😝公众号😝想全栈的小冷,分享一些技术上的文章,以及解决问题的经验
当前专栏JUC系列

集合类不安全

List不安全

并发 arrayList是不安全的
解决方案:

1. vector 线程安全,因为新增方法前 带了 sync关键字,这个方法并不是最优解
2. 集合工具类 Collections.synchronizedList(new ArrayList<>());
3. JUC包下 CopyOnWriteArrayList<>()

CopyOnWrite 写入时复制,COW 计算机程序设计领域的一种优化策略

如 : 比如多个调用者调用同一个list,读取的时候,固定的,写入(覆盖)

在写入的时候 避免覆盖,造成数据问题; 读写分离

问题: 为什么不用vector 为什么呢?

答 : 因为只要使用 sync关键字,效率都会低一些,而CopyOnWriteArrayList,底层的方法是用的lock锁

//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
    public static void main(String[] args) {
        //并发 arrayList是不安全的
        /*  解决方案
         * 1. vector 线程安全,因为新增方法前 带了 sync关键字,这个方法并不是最优解
         * 2. 集合工具类 Collections.synchronizedList(new ArrayList<>());
         * 3. JUC包下 CopyOnWriteArrayList<>()
         *  CopyOnWrite 写入时复制,COW 计算机程序设计领域的一种优化策略
         * 如 : 比如多个调用者调用同一个list,读取的时候,固定的,写入(覆盖)
         * 在写入的时候 避免覆盖,造成数据问题;
         * 读写分离
         *
         * 问题: 为什么不用vector 为什么呢? 因为只要使用 sync关键字,效率都会低一些,而CopyOnWriteArrayList,底层的方法是用的lock锁
         * */
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 1; i <= 600; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}
写入时复制的add方法的原理
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock(); //上锁,只允许一个线程进入
        try {
            Object[] elements = getArray(); // 获得当前数组对象
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝到一个新的数组中
            newElements[len] = e;//插入数据元素
            setArray(newElements);//将新的数组对象设置回去
            return true;
        } finally {
            lock.unlock();//释放锁
        }
    }
Set不安全

解决方案 :

  • 首先就是 Collections工具类 Collections.synchronizedSet(new HashSet());
  • 就是和集合再同一个解决方案里的 Set set = new CopyOnWriteArraySet();
public class SetTest {
    //java.util.ConcurrentModificationException 同理和集合没有什么区别
    public static void main(String[] args) {
        /* 如何解决hashset 线程不安全?
         * 1. 首先就是 Collections工具类 Collections.synchronizedSet(new HashSet());
         * 2. 就是和集合再同一个解决方案里的  Set set = new CopyOnWriteArraySet();
         * */
        Set set = new CopyOnWriteArraySet();
        for (int i = 1; i <= 600; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}

HashSet的底层 是 hashmap

public HashSet(){
    map = new HashMap<>();
}

//add set 的本质就是map key 是无法重复的
public boolean add(E e){
    return map.put(e,PRESENT)==null;
}

private static final Object PRESENT = new Object //不变的值
Map类不安全

HashMap原理简单回顾

image-20220302112806560

解决方案:

  • Map<String, String> map = new ConcurrentHashMap<>();
public class mapTest {
    //java.util.ConcurrentModificationException
    public static void main(String[] args) {
        //    map是这样用的吗 不是 工作中很少使用 Hashmap
        //    默认等价于什么 new HashMap<>(16,0.75);
        /*解决方案
         *
         * */
        Map<String, String> map = new ConcurrentHashMap<>();
        for (int i = 1; i <= 500; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }

    }
}
ConcurrentHashMap原理
        ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
ConcurrentHashMap的内部结构

image-20220309163044870

从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上),所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

相关文章
|
6月前
|
存储 安全 Java
线程安全集合类
线程安全集合类
38 1
|
6月前
|
安全 Java 容器
线程安全的集合类
线程安全的集合类
|
2月前
|
存储 安全 算法
JUC集合: ConcurrentLinkedQueue详解
与此同时,它的无界特性在使用时需要注意,因为过多的数据累积可能会导致内存消耗过大。合理应用 `ConcurrentLinkedQueue` 不仅可以提升应用性能,还能提高程序在并发环境下的可靠性。在实际的开发过程中,合理选择适当的并发容器对于构建高效稳定的系统至关重要。
41 2
|
6月前
|
编解码 安全 算法
Java多线程基础-18:线程安全的集合类与ConcurrentHashMap
如果这些单线程中的集合类确实需要在多线程中使用,该怎么办呢?思路有两个: 最直接的方式:使用锁,手动保证。如多个线程修改ArrayList对象,此时就可能有问题,就可以给修改操作进行加锁。但手动加锁的方式并不是很方便,因此标准库还提供了一些线程安全的集合类。
94 4
|
6月前
|
安全 Java 容器
Java 多线程系列Ⅶ(线程安全集合类)
Java 多线程系列Ⅶ(线程安全集合类)
|
6月前
|
安全 Java 容器
Java一分钟之-并发编程:线程安全的集合类
【5月更文挑战第19天】Java提供线程安全集合类以解决并发环境中的数据一致性问题。例如,Vector是线程安全但效率低;可以使用Collections.synchronizedXxx将ArrayList或HashMap同步;ConcurrentHashMap是高效线程安全的映射;CopyOnWriteArrayList和CopyOnWriteArraySet适合读多写少场景;LinkedBlockingQueue是生产者-消费者模型中的线程安全队列。注意,过度同步可能影响性能,应尽量减少共享状态并利用并发工具类。
62 2
|
6月前
|
安全 Java 容器
Java一分钟之-并发编程:并发容器(ConcurrentHashMap, CopyOnWriteArrayList)
【5月更文挑战第18天】本文探讨了Java并发编程中的`ConcurrentHashMap`和`CopyOnWriteArrayList`,两者为多线程数据共享提供高效、线程安全的解决方案。`ConcurrentHashMap`采用分段锁策略,而`CopyOnWriteArrayList`适合读多写少的场景。注意,`ConcurrentHashMap`的`forEach`需避免手动同步,且并发修改时可能导致`ConcurrentModificationException`。`CopyOnWriteArrayList`在写操作时会复制数组。理解和正确使用这些特性是优化并发性能的关键。
60 1
|
5月前
|
安全 Java 容器
多线程(进阶四:线程安全的集合类)
多线程(进阶四:线程安全的集合类)
33 0
|
存储 安全 算法
一天一个 JUC 工具类 -- 并发集合
使用JUC工具包中的并发集合,我们可以避免手动处理锁和同步的复杂性,从而降低出现线程安全问题的概率。这些并发集合通过内部采用高效的算法和数据结构来优化并发操作,从而提供更好的性能和扩展性。
|
安全 Java 索引
JUC第十六讲:JUC集合: CopyOnWriteArrayList详解
JUC第十六讲:JUC集合: CopyOnWriteArrayList详解