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