java常见面试考点(二十六):集合类不安全

java常见面试考点

往期文章推荐:
  java常见面试考点(二十一):单点登录
  java常见面试考点(二十二):购物车实现
  java常见面试考点(二十三):消息队列
  java常见面试考点(二十四):强引用,软引用,虚引用,弱引用
  java常见面试考点(二十五):CAS是什么


【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权);

本博客的内容来自于:java常见面试考点(二十六):集合类不安全

学习、合作与交流联系q384660495;

本博客的内容仅供学习与参考,并非营利;


一、集合类不安全问题之并发修改异常

多线程环境下,对同一个集合进行操作可能会出现异常,代码如下:

public class UnsafeList2 {
    public static void main(String[] args) {
 
        List<String> list=new ArrayList<>();
        for (int i = 1; i <=50; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

异常
通过以上两段代码,我们可以看到,在多线程情况下会报java.util.ConcurrentModificationException并发修改异常。

而ArrayList是jdk1.2出现的,是一个不加锁的集合类,并发性上升,但是牺牲多线程的安全性为代价。再看ArrayList源码发现add方法没有加锁,所以多线程情况下出现线程异常问题。

产生原因

多线程同时操作时,可能有以下几种情况:

  1. 各个线程都读:不影响,前提是只有读;
  2. 各个线程都写:会出现问题,有俩种情况:
    1. 值覆盖问题,因为ArrayList底层是数组,写入值的时候要先计算到一个下标位置,然后去赋值,多线程可能会获得同一个下标,发生值覆盖问题。
    2. 空指针异常,因为ArrayList底层是数组,写入值在数组满时需要扩容,在扩容还没有完成的时候,新的下标已经计算出来并要去插入,那么就会出现空指针异常。
  3. 有的读有的写:不仅包含了情况2的所有问题,此外还有:
    1. 如果多线程有的读有的写,对于ArrayList底层,某些情况下,对象是不允许修改的,如果修改了,后面调用某些方法时,然后就会抛出ConcurrentModificationException
    2. 具体一下,因为源码里,写操作对集合修改是写,而next,remove等Itr的遍历操作的时候会通过当前集合的修改次数与Itr对象创建时记录的次数校验是否被修改,如果不一致则说明还有别的线程再改,就会抛出异常。
    3. JDK作者说了,会抛出这个异常的叫做fail-fast iterator

第三种情况其实就是上面问题的产生原因。

二、集合类不安全问题之List

  1. 使用List接口下的实现集合Vector 类,但是该类是JDK1.0出现的,其add()是syncronized修饰的。数据一致性可以得到保证,但是并发性会下降。
  2. 数据量小的时候,使用工具类 Collections中的synchronizedList将其变为安全的集合类,其中还有构建set,map集合安全类的方法。
  3. JUC包中的CopyOnWriteArrayList,推荐使用

详解CopyOnWriteArrayList

CopyOnWriteArrayList写时复制容器。 往一个容器添加元素的时候,不直接往当前容器 Object[] 添加, 而是先将当前容器 Object[] 进行copy, 复制出一个新的容器 Object[] newElements, 然后往新的容器Object[] newElements里添加元素, 添加完元素之后, 再将原容器的引用指向新的容器 setArray(newElements)。这样做的好处是可以对 CopyOnWrite容器进行并发的读, 而不需要加锁, 因为当前容器不会添加任何元素. 所以 CopyOnWrite容器 也是一种读写分离的思想, 读和写不同的容器。源码如下:
源码

三、集合类不安全问题之Set

HashSet底层数据结构是HashMap(源码构造器里面 new HashMap()),其add方法实际上return map.put(e,PRESENT)==null; PRESENT实际上就是一个object常量,所以实际上就是HashMap的keys。

  1. 使用工具类 Collections => Set set = Collections.synchronizedSet(new HashSet<>());
  2. JUC包 中的Set set = new CopyOnWriteArraySet<>();

详解CopyOnWriteArraySet

CopyOnWriteArraySet和CopyOnWriteArrayList类似,其实CopyOnWriteSet底层包含一个CopyOnWriteList,几乎所有操作都是借助CopyOnWriteList实现的,就像HashSet包含HashMap。而CopyOnWriteArrayList本质是个动态数组队列,所以CopyOnWriteArraySet相当于通过通过动态数组实现的“集合”! CopyOnWriteArrayList中允许有重复的元素;但是,CopyOnWriteArraySet是一个集合,所以它不能有重复集合。因此,CopyOnWriteArrayList额外提供了addIfAbsent()和addAllAbsent()这两个添加元素的API,通过这些API来添加元素时,只有当元素不存在时才执行添加操作!至于CopyOnWriteArraySet的“线程安全”机制,和CopyOnWriteArrayList一样,是通过volatile和互斥锁来实现的。
源码
源码

四、集合类不安全问题之Map

hashmap出现的问题比较复杂,参考这篇文章HashMap线程不安全的体现

  1. 使用Collections工具类的synchronizedMap()方法
  2. 使用HashTable,但HashTable也是一个古老的技术,它的所有方法也是都加了锁,并发性不好,不推荐使用。独占锁。
  3. 使用ConcurrentHashMap(J.U.C提供),java没有为map提供写时复制的类,所以我们使用ConcurrentHashMap来解决map类线程安全的问题。ConcureentHashMap同步容器类是java5增加的一个线程安全的哈希表。对于多线程的操作,介于HashMap与Hashtable之间。内部采用“锁分段”机制替代Hashtable的独占锁。进而提高性能。

参考这篇文章ConcurrentHashMap底层实现原理(JDK1.7 & 1.8)ConcureentHashMap详解

五、参考资料

java的各种集合为什么不安全(List、Set、Map)以及代替方案
juc学习四(集合不安全问题)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏天的爱人是绿色

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值