java的各种集合为什么不安全(List、Set、Map)?( 二 )
显然能传入参数的这些基本集合类都是线程不安全的 。
- 第三种就是 , 直接使用 juc 包里面的 , CopyOnWriteArrayList() 类 , 这个类就是并发包给我们提供的线程安全的列表类 。 1.4里介绍了这个集合 。
首先 , 按照前面的我们的分析 , 只要涉及了写的操作 , 和读或者写搭配的多线程情况 , 就会出现问题 , 那么多线程同时读却不会出现问题 , 因此相比较于直接都加上 synchronized 的方式 , 他的思想就是:读写分离 。 这个思想在数据库对于高并发的架构层面也有一样的设计 。
这样一来 , 对于这个 List 集合来说 , 分为不同操作的保证线程安全的策略 , 就能够保证更好的性能 。
写的方法 , 我们首先可以看 add 方法源码:
文章插图
步骤很清楚 , 如果有了写操作 , 需要加锁:
- 加锁
- 获取到当前的集合数组;
- 计算长度;
- 调用 Arrays.copyOf 方法进行添加操作 , 每次只添加一个元素进去;
- 修改引用 , 更新最新的集合;
- return true 。
- 解锁
文章插图
可以看到是一个普通的 Object 。
那么加锁的时候就用 synchronized 对 Object 进行加锁 , 没有采用 juc 的 ReetrantLock , 注释li也写了 , 偏向于使用内置的 monitor 也就是 synchronized 底层 monitor 锁 , 这一点也充分说明了 synchronized 的性能更新使得源码作者使用它 。
这个方法是处理最直接的 , 其他对应的写操作:remove、set等等也是一样的基础流程 。
我们再来看看读操作 get 方法:
文章插图
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();}}}
就会看到一样的错误:文章插图
2|22.2 出现问题的原因其实从出现 ConcurrentModificationException 异常来看 , 我们可以猜测是和 List 类似的原因导致的异常 。
可以看到 , 源码里面 , Set 的底层维护的是一个 HashMap 来实现 。 对于遍历操作来说 , 都是一样的使用了 fail-fast iterator 迭代器 , 因此会出现这个异常 。
另外 , 因为 HashSet 的底层是 HashMap, 本质上 , 对于每一个 key, 保证唯一 , 使用了一个 value 为 PRESENT 常量的键值对进行存储 。
文章插图
put 的过程也是调用 map 的 put 方法 。
2|32.3 解决方案
- List 有对应的 Vector 可用 , 本来就是线程安全的集合 , 但是 Set 没有;
- 数据量小的时候 , 使用 Collections.synchronizedSet(new HashSet<>()) 这种方式 , 来包裹这个集合 , 上面我们使用 List 的时候也有类似的方法;
推荐阅读
- 计算机专业大一下学期,该选择学习Java还是Python
- 未来想进入AI领域,该学习Python还是Java大数据开发
- 学习大数据是否需要学习JavaEE
- 从事Java开发时发现基础差,是否应该选择辞职自学一段时间
- 2021年Java和Python的应用趋势会有什么变化?
- 普通大学计算机专业的本科生,该选择主攻前端还是Java
- Java语言会不会随着容器的兴起而衰落
- 大一有考研计算机专业的打算,该学习C++还是Java
- keytypes(org.Hs.eg.db)基因的各种样子
- 计算机专业的同学在掌握了Java之后,还可以学习哪门后端语言