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基础知识回顾,还记得吗?
- mybatis sharding-jdbc Java8日期
- 近20年Windows权限提升集合
- 树莓派控制步进电机-TB6600-Java版本
- 德州点创教育JavaScript正则表达式授课大纲
- 如何编写JAVA小白第一个程序
- java安全编码指南之:异常简介
- Java学习路线图
- Java核心技术点有哪些 有没有什么书籍推荐
- Java函数式编码结构-好程序员