简单来说:这两个都是 Java 自带的、多线程一起用也不会乱的数据盒子。区别就是:一个盒子是放【一对一对的数据(名字 + 内容)】,一个盒子是放【一排顺序的数据】。
一、ConcurrentHashMap(存键值对,像字典)
想象它是一本字典:左边是单词(key,不能重复),右边是单词解释(value)。
那么它怎么保证多人同时使用不乱?
旧版本 Hashtable:整本字典上一把大锁。只要有一个人在修改,其他人不管查哪里、改哪里,全都要排队等着,很慢。
ConcurrentHashMap 则是把这本字典里面分成好多好多小格子(哈希桶)。
- 大家查数据的时候,大多情况不用上锁,可以一起查;
- 如果有人要修改某一个格子里面的内容:只锁住这一小格,别的格子别人照样可以读、可以改,互不影响。
举例:A 修改 "苹果" 这一格,B 同时修改 "香蕉" 这一格,两个人不用互相等。
特点:
- 不能放空:单词和解释都不能是空。 原因:你查一个单词,如果返回空,分不清是【这个单词不存在】,还是【单词对应的解释本身就是空】,多人一起用的时候会搞混。
- 你一边翻字典(遍历),别人一边修改:你看到的不一定是最新内容,但是不会直接报错崩溃。
- 写东西的速度中等,不算特别慢。修改的时候只影响一小部分数据。
java
// 简单例子:字典,水果-数量
ConcurrentHashMap<String, Integer> fruitMap = new ConcurrentHashMap<>();
fruitMap.put("苹果", 10);
fruitMap.put("香蕉", 20);
// 如果不存在才放进去,已经有了就不改
fruitMap.putIfAbsent("苹果", 99);
System.out.println(fruitMap.get("苹果")); // 输出10
二、CopyOnWriteArrayList(有序列表,像队伍排队)
想象它是一条队伍:张三、李四、王五,有先后顺序,可以重复名字,可以按第 1 个、第 2 个找人。
那么它怎么保证多人同时使用不乱?
核心规则:读旧队伍,改副本队伍
- 所有人查看队伍、找人的时候,直接看现在这个队伍,不用排队、不用上锁,速度很快;
- 只要有人想改动队伍(加人、踢人、修改某个人):
- 先上锁,防止好几个人同时改;
- 完整复制一份一模一样的新队伍;
- 在新队伍上面做修改;
- 改完之后,把大家的视线切换到这个新队伍;
- 旧队伍就放着,等没人用了自动清理。
重点:修改的时候,要复制整个队伍!队伍越长,复制越费劲、越慢。
特点:
- 可以放 null(队伍里可以有空位置);
- 如果你正在看旧队伍,别人已经换了新队伍:你看到的还是旧的人员名单,不会报错;
- 读飞快;修改非常慢。队伍一大,每次增删都要复制一大串,很耗性能。
java
// 简单例子:人员名单
CopyOnWriteArrayList<String> nameList = new CopyOnWriteArrayList<>();
nameList.add("张三");
nameList.add("李四");
nameList.set(1, "小李"); // 把第二个改成小李
System.out.println(nameList.get(1)); // 小李
简易对比表:
| 对比 | ConcurrentHashMap | CopyOnWriteArrayList |
|---|---|---|
| 里面存什么 | 一对一对数据(名字:内容) | 一排有序的数据,有先后顺序 |
| 多人读写思路 | 盒子分成很多小格子,改哪个格子就锁哪个小格子 | 读原来的队伍;修改时复制整支队伍,改副本 |
| 读速度 | 快 | 非常快(不用上锁) |
| 修改速度 | 还行,只影响一小块 | 很慢!每次改都要全部复制 |
| 能不能放空 | 都不能空 | 可以放空 |
| 适合场景 | 存键值信息,读多、写也有一些 | 名单很少改动,大家反复查看 |
简易记忆方法:顾名思义。
ConcurrentHashMap:Concurrent代表并发,HashMap是键值对集合;也就是多线程可以安全同时读写的键值对容器 。CopyOnWriteArrayList:CopyOnWrite含义是写的时候复制 ,ArrayList代表有序列表;也就是修改数据时复制数组的有序线程安全列表。
小结
- ConcurrentHashMap 是多线程安全的字典,把数据分成很多小块,修改只锁一小块,读写速度比较均衡,不能存空值。
- CopyOnWriteArrayList 是多线程安全的名单。查看名单不用上锁;修改名单要复制一份新名单再改,读很快,修改很慢,适合很少改动的列表。