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


显然能传入参数的这些基本集合类都是线程不安全的 。

  • 第三种就是 , 直接使用 juc 包里面的 , CopyOnWriteArrayList() 类 , 这个类就是并发包给我们提供的线程安全的列表类 。 1.4里介绍了这个集合 。
1|41.4 CopyOnWriteArrayList对于 CopyOnWriteArrayList 类 , 名字上就可以听的出来 , 写时复制的列表 。
首先 , 按照前面的我们的分析 , 只要涉及了写的操作 , 和读或者写搭配的多线程情况 , 就会出现问题 , 那么多线程同时读却不会出现问题 , 因此相比较于直接都加上 synchronized 的方式 , 他的思想就是:读写分离 。 这个思想在数据库对于高并发的架构层面也有一样的设计 。
这样一来 , 对于这个 List 集合来说 , 分为不同操作的保证线程安全的策略 , 就能够保证更好的性能 。
写的方法 , 我们首先可以看 add 方法源码:
java的各种集合为什么不安全(List、Set、Map)?文章插图
步骤很清楚 , 如果有了写操作 , 需要加锁:
  1. 加锁
  2. 获取到当前的集合数组;
  3. 计算长度;
  4. 调用 Arrays.copyOf 方法进行添加操作 , 每次只添加一个元素进去;
  5. 修改引用 , 更新最新的集合;
  6. return true 。
  7. 解锁
其中的 lock 在源码里就是一个:
java的各种集合为什么不安全(List、Set、Map)?文章插图
可以看到是一个普通的 Object 。
那么加锁的时候就用 synchronized 对 Object 进行加锁 , 没有采用 juc 的 ReetrantLock , 注释li也写了 , 偏向于使用内置的 monitor 也就是 synchronized 底层 monitor 锁 , 这一点也充分说明了 synchronized 的性能更新使得源码作者使用它 。
这个方法是处理最直接的 , 其他对应的写操作:remove、set等等也是一样的基础流程 。
我们再来看看读操作 get 方法:
java的各种集合为什么不安全(List、Set、Map)?文章插图
2|0二、HashSet 的不安全2|12.1 问题及原因我们还是用 List 一样的测试代码;
public class TestSet {public static void main(String[] args) {HashSet set = new HashSet<>();for (int i = 0; i < 100; i++){new Thread(()->{set.add(UUID.randomUUID().toString().substring(0,8));System.out.println(set);},String.valueOf(i)).start();}}}就会看到一样的错误:
java的各种集合为什么不安全(List、Set、Map)?文章插图
2|22.2 出现问题的原因其实从出现 ConcurrentModificationException 异常来看 , 我们可以猜测是和 List 类似的原因导致的异常 。
可以看到 , 源码里面 , Set 的底层维护的是一个 HashMap 来实现 。 对于遍历操作来说 , 都是一样的使用了 fail-fast iterator 迭代器 , 因此会出现这个异常 。
另外 , 因为 HashSet 的底层是 HashMap, 本质上 , 对于每一个 key, 保证唯一 , 使用了一个 value 为 PRESENT 常量的键值对进行存储 。
java的各种集合为什么不安全(List、Set、Map)?文章插图
put 的过程也是调用 map 的 put 方法 。
2|32.3 解决方案