java的各种集合为什么不安全(List、Set、Map)?( 三 )

  • 同样的 , juc包为我们提供了新的线程安全集合 CopyOnWriteArraySet() 。
  • 2|42.4 CopyOnWriteArraySet
    1. 按照前面的思路 , List 的对应线程安全集合是在 List 集合的数组基础上进行加锁的相关操作 。
    2. 那么 Set 既然底层是 HashMap , 对应的线程安全集合就应该是对 HashMap 的线程安全集合进行加锁 , 或者说直接用 ConcurrentHashMap 集合来实现 CopyOnWriteArraySet。
    3. 但事实上 , 源码并不是这么做的 。
    从名字来看 , 和 ConcurrentHashMap 也没有什么关系 , 而是类似 CopyOnWriteArrayList 的命名 , 说明是读写单独处理 , 来让他成为线程安全的集合 , 那为什么是 ArraySet 多一个 array 修饰语呢?
    java的各种集合为什么不安全(List、Set、Map)?文章插图
    可以看到 , 他的思路没有顺延 util 包的 HashSet 的实现思路 , 而是直接使用了 CopyOnWriteArrayList 作为底层数据结构 。 也就是说没有利用 Map 的键值对映射的特性来保证 set 的唯一性 , 而是用一个数组为基底的列表来实现 。 (那显然在去重方面就要做额外的操作了 。 )
    然后每一个实现的方法都很简单 , 基本是直接调用了 CopyOnWriteArrayList 的方法:
    java的各种集合为什么不安全(List、Set、Map)?文章插图
    我们最担心的可能 产生问题的 remove 和 add 方法 , 也是使用了 CopyOnWriteArrayList 的方法:
    而保证 set 的不重复性质的关键 , 显然就在于 CopyOnWriteArrayList 的 addIfAbsent 方法 , 我们还是点进 CopyOnWriteArrayList 源码看一看这个方法的实现:
    java的各种集合为什么不安全(List、Set、Map)?文章插图
    其中的 indexOfRange 方法:
    java的各种集合为什么不安全(List、Set、Map)?文章插图
    可以看到 , 也是加了 Monitor 锁来进行的 , 整个过程是这样的:
    1. 获取本来的 set, 是一个数组 , 以快照形式返回当前的数组;
    2. indexOfRange 方法通过遍历查找查找元素出现位置 , addIfAbsent方法完成不存在则加入 , 如果前一个为 false 后一个就不会执行;
    3. 加锁;
    4. current 再次获取一次当前的快照 , 因为有可能第一次判断的过程有了其他线程的插入或者修改操作 , 此时已经不像等 , 就进入分支进行判断是否存在;
    5. 否则就要加入这个元素 , 和 CopyOnWriteArrayList 添加元素的最后操作是一样的;
    6. 解锁 。
    总结一下就是 , 线程安全的 Set 集合完全利用了 CopyOnWriteArrayList 集合的方法 , 对应的操作也是读写分别处理 , 写时复制的策略 , 通过 jvm 层面的锁来保证安全 , 那么保证不重复的方法就是遍历进行比较 。
    这样看来 , 相比于基于 HashMap 的去重方法 , 效率肯定会降低 , 不过如果基于线程安全的 HashMap, 插入操作从hash、比较、到考虑扩容各方面会因为加锁的过程更复杂 , 而对于一个不重复的 Set 来说 , 完全没必要 , 所以应该综合考虑之下采用了 List 为基础 , 暴力循环去重 。
    3|0三、HashMap 的线程不安全关于 HashMap 的相关问题 , 源码里已经分析过 , 大体是这样的 。
    不安全:
    1. 普通读写不一致问题;
    2. 死循环问题;
    3. ConcurrentModificationException 异常 。
    【java的各种集合为什么不安全(List、Set、Map)?】解决:
    1. util包的Hashtable集合线程安全;