为什么ConcurrentHashMap不允许插入null值,而HashMap却可以?

245 阅读2分钟

为什么 ConcurrentHashMap 不允许插入 null值?

默认条件:ConcurrentHashMap在多线程环境,HashMap在单线程环境

ConcurrentHashMap 的 key 和 value 不能为 null 主要是为了避免二义性。null 是一个特殊的值,表示没有对象或没有引用。(如果你用 null 作为键,那么你就无法区分这个键是否存在于ConcurrentHashMap 中,还是根本没有这个键。)同样,如果你用 null 作为值,那么你就无法区分这个值是否是真正存储ConcurrentHashMap 中的,还是因为找不到对应的键而返回的。

拿 get 方法取值来说,返回的结果为 null 存在两种情况:

  • 值没有在集合中 ;
  • 值本身就是 null。

这也就是二义性的由来。

多线程环境下,存在一个线程操作该 ConcurrentHashMap 时,其他的线程将该 ConcurrentHashMap 修改的情况,所以无法通过 containsKey(key) 来判断否存在这个键值对,也就没办法解决二义性问题了。

举个例子,现在有线程 T1 调用了 ConcurrentHashMap 的 containsKey(key) 方法, 我们期望返回的结果是 false,也就是说,T1 并没有往 ConcurrentHashMap 中 put null(空)值。 但是,恰恰出了个意外,在线程 T1 还没有得到返回结果之前,线程 T2 又调用了 ConcurrentHashMap 的 put() 方法,插入了一个 Key,并且存入的 Value 是 null(空) 值。那么,线程 T1 最终得到的返回结果就变成 true 了。

如果你确实需要在 ConcurrentHashMap 中使用 null 的话,可以使用一个特殊的静态空对象来代替 null。

public static final Object NULL = new Object();

与此形成对比的是,HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个。如果传入 null 作为参数,就会返回 hash 值为 0 的位置的值。HashMap 的设计是给单线程使用的,所以如果取到 null(空) 值,我们可以通过HashMap 的 containsKey(key)方 法来区分这个 null(空) 值到底是插入值是 null(空),还是本就没有才返回的 null(空) 值。单线程环境下,不存在一个线程操作该 HashMap 时,其他的线程将该 HashMap 修改的情况,所以可以通过 contains(key)来做判断是否存在这个键值对,从而做相应的处理,也就不存在二义性问题。

也就是说,多线程下无法正确判定键值对是否存在(存在其他线程修改的情况),单线程是可以的(不存在其他线程修改的情况)。