简述 ConcurrentHashMap 和 CopyOnWriteArrayList 的区别以及各自适用场景

17 阅读4分钟

简单来说:这两个都是 Java 自带的、多线程一起用也不会乱的数据盒子。区别就是:一个盒子是放【一对一对的数据(名字 + 内容)】,一个盒子是放【一排顺序的数据】。

一、ConcurrentHashMap(存键值对,像字典)

想象它是一本字典:左边是单词(key,不能重复),右边是单词解释(value)。

那么它怎么保证多人同时使用不乱?

旧版本 Hashtable:整本字典上一把大锁。只要有一个人在修改,其他人不管查哪里、改哪里,全都要排队等着,很慢。

ConcurrentHashMap 则是把这本字典里面分成好多好多小格子(哈希桶)。

  • 大家查数据的时候,大多情况不用上锁,可以一起查;
  • 如果有人要修改某一个格子里面的内容:只锁住这一小格,别的格子别人照样可以读、可以改,互不影响。

举例:A 修改 “苹果” 这一格,B 同时修改 “香蕉” 这一格,两个人不用互相等。

特点:

  1. 不能放空:单词和解释都不能是空。 原因:你查一个单词,如果返回空,分不清是【这个单词不存在】,还是【单词对应的解释本身就是空】,多人一起用的时候会搞混。
  2. 你一边翻字典(遍历),别人一边修改:你看到的不一定是最新内容,但是不会直接报错崩溃。
  3. 写东西的速度中等,不算特别慢。修改的时候只影响一小部分数据。
// 简单例子:字典,水果-数量
ConcurrentHashMap<String, Integer> fruitMap = new ConcurrentHashMap<>();
fruitMap.put("苹果", 10);
fruitMap.put("香蕉", 20);

// 如果不存在才放进去,已经有了就不改
fruitMap.putIfAbsent("苹果", 99);
System.out.println(fruitMap.get("苹果")); // 输出10

二、CopyOnWriteArrayList(有序列表,像队伍排队)

想象它是一条队伍:张三、李四、王五,有先后顺序,可以重复名字,可以按第 1 个、第 2 个找人。

那么它怎么保证多人同时使用不乱?

核心规则:读旧队伍,改副本队伍

  • 所有人查看队伍、找人的时候,直接看现在这个队伍,不用排队、不用上锁,速度很快;
  • 只要有人想改动队伍(加人、踢人、修改某个人):
    1. 先上锁,防止好几个人同时改;
    2. 完整复制一份一模一样的新队伍;
    3. 在新队伍上面做修改;
    4. 改完之后,把大家的视线切换到这个新队伍;
    5. 旧队伍就放着,等没人用了自动清理。

重点:修改的时候,要复制整个队伍!队伍越长,复制越费劲、越慢。

特点:

  1. 可以放 null(队伍里可以有空位置);
  2. 如果你正在看旧队伍,别人已经换了新队伍:你看到的还是旧的人员名单,不会报错;
  3. 读飞快;修改非常慢。队伍一大,每次增删都要复制一大串,很耗性能。
// 简单例子:人员名单
CopyOnWriteArrayList<String> nameList = new CopyOnWriteArrayList<>();
nameList.add("张三");
nameList.add("李四");
nameList.set(1, "小李"); // 把第二个改成小李
System.out.println(nameList.get(1)); // 小李

简易对比表:

对比ConcurrentHashMapCopyOnWriteArrayList
里面存什么一对一对数据(名字:内容)一排有序的数据,有先后顺序
多人读写思路盒子分成很多小格子,改哪个格子就锁哪个小格子读原来的队伍;修改时复制整支队伍,改副本
读速度快非常快(不用上锁)
修改速度还行,只影响一小块很慢!每次改都要全部复制
能不能放空都不能空可以放空
适合场景存键值信息,读多、写也有一些名单很少改动,大家反复查看

简易记忆方法:顾名思义。 ConcurrentHashMap:Concurrent 代表并发,HashMap 是键值对集合;也就是多线程可以安全同时读写的键值对容器。 CopyOnWriteArrayList:CopyOnWrite含义是写的时候复制,ArrayList代表有序列表;也就是修改数据时复制数组的有序线程安全列表。

小结

  • ConcurrentHashMap 是多线程安全的字典,把数据分成很多小块,修改只锁一小块,读写速度比较均衡,不能存空值。
  • CopyOnWriteArrayList 是多线程安全的名单。查看名单不用上锁;修改名单要复制一份新名单再改,读很快,修改很慢,适合很少改动的列表。