美团面试拷打:ConcurrentHashMap 为何不能插入 null?HashMap 为何可以?

面试,拷打,concurrenthashmap,为何,不能,插入,null,hashmap,可以 · 浏览次数 : 450

小编点评

**ConcurrentHashMap 为什么 key 和 value 不能为 null?** 由于 ConcurrentHashMap 是线程安全的,因此它在处理操作时不能允许 key 或 value 为 null。如果 key 或 value 为 null,会导致 ambiguous 的结果,因为 null 是一个特殊值表示没有对象或没有引用。 **ConcurrentHashMap 能保证复合操作的原子性吗?** ConcurrentHashMap 能保证复合操作的原子性,但它并不保证所有复合操作都是原子性的。复合操作是指由多个基本操作组成的操作,例如先判断某个键是否存在,然后根据结果进行插入或更新put(key, value)。这种操作在执行过程中可能会被其他线程打断,导致结果不符合预期。例如,有两个线程 A 和 B 同时对 ConcurrentHashMap 进行复合操作,如下:// 线程 Aif (!map.containsKey(key)) {map.put(key, value);}// 线程 Bif (!map.containsKey(key)) {map.put(key, anotherValue);}如果线程 A 和 B 的执行顺序是这样:线程 A 判断 map 中不存在 key线程 B 判断 map 中不存在 key线程 B 将 (key, anotherValue) 插入 map线程 A 将 (key, value) 插入 map那么最终的结果是 (key, value),而不是预期的 (key, anotherValue)。这就是复合操作的非原子性导致的问题。 **其他建议** * 可以使用一个特殊的静态空对象来代替 null。 * 使用原子性的复合操作方法,例如 putIfAbsent、compute、computeIfAbsent、computeIfPresent、merge等。

正文

周末的时候,有一位小伙伴提了一些关于 ConcurrentHashMap 的问题,都是他最近面试遇到的。原提问如下:

整个提问看着非常复杂,其实归纳来说就是两个问题:

  1. ConcurrentHashMap 为什么 key 和 value 不能为 null?
  2. ConcurrentHashMap 能保证复合操作的原子性吗?

下面我会以此提供这两个问题的详细答案,希望对你有帮助。

ConcurrentHashMap 为什么 key 和 value 不能为 null?

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

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

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

这也就是二义性的由来。

具体可以参考 ConcurrentHashMap 源码分析

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

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

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

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

public static final Object NULL = new Object();

最后,再分享一下 ConcurrentHashMap 作者本人 (Doug Lea)对于这个问题的回答:

The main reason that nulls aren't allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can't be accommodated. The main one is that if map.get(key) returns null, you can't detect whether the key explicitly maps to null vs the key isn't mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls.

翻译过来之后的,大致意思还是单线程下可以容忍歧义,而多线程下无法容忍。

ConcurrentHashMap 能保证复合操作的原子性吗?

ConcurrentHashMap 是线程安全的,意味着它可以保证多个线程同时对它进行读写操作时,不会出现数据不一致的情况,也不会导致 JDK1.7 及之前版本的 HashMap 多线程操作导致死循环问题。但是,这并不意味着它可以保证所有的复合操作都是原子性的,一定不要搞混了!

复合操作是指由多个基本操作(如putgetremovecontainsKey等)组成的操作,例如先判断某个键是否存在containsKey(key),然后根据结果进行插入或更新put(key, value)。这种操作在执行过程中可能会被其他线程打断,导致结果不符合预期。

例如,有两个线程 A 和 B 同时对 ConcurrentHashMap 进行复合操作,如下:

// 线程 A
if (!map.containsKey(key)) {
map.put(key, value);
}
// 线程 B
if (!map.containsKey(key)) {
map.put(key, anotherValue);
}

如果线程 A 和 B 的执行顺序是这样:

  1. 线程 A 判断 map 中不存在 key
  2. 线程 B 判断 map 中不存在 key
  3. 线程 B 将 (key, anotherValue) 插入 map
  4. 线程 A 将 (key, value) 插入 map

那么最终的结果是 (key, value),而不是预期的 (key, anotherValue)。这就是复合操作的非原子性导致的问题。

那如何保证 ConcurrentHashMap 复合操作的原子性呢?

ConcurrentHashMap 提供了一些原子性的复合操作,如 putIfAbsentcomputecomputeIfAbsentcomputeIfPresentmerge等。这些方法都可以接受一个函数作为参数,根据给定的 key 和 value 来计算一个新的 value,并且将其更新到 map 中。

上面的代码可以改写为:

// 线程 A
map.putIfAbsent(key, value);
// 线程 B
map.putIfAbsent(key, anotherValue);

或者:

// 线程 A
map.computeIfAbsent(key, k -> value);
// 线程 B
map.computeIfAbsent(key, k -> anotherValue);

很多同学可能会说了,这种情况也能加锁同步呀!确实可以,但不建议使用加锁的同步机制,违背了使用 ConcurrentHashMap 的初衷。在使用 ConcurrentHashMap 的时候,尽量使用这些原子性的复合操作方法来保证原子性。

与美团面试拷打:ConcurrentHashMap 为何不能插入 null?HashMap 为何可以?相似的内容:

美团面试拷打:ConcurrentHashMap 为何不能插入 null?HashMap 为何可以?

周末的时候,有一位小伙伴提了一些关于 `ConcurrentHashMap` 的问题,都是他最近面试遇到的。原提问如下: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9fa94f132705416a8e07e33907544113~tp

美团面试:说说Netty的零拷贝技术?

零拷贝技术(Zero-Copy)是一个大家耳熟能详的技术名词了,它主要用于提升 IO(Input & Output)的传输性能。 那么问题来了,为什么零拷贝技术能提升 IO 性能? 1.零拷贝技术和性能 在传统的 IO 操作中,当我们需要读取并传输数据时,我们需要在用户态(用户空间)和内核态(内核空

美团面试:如何实现线程任务编排?

线程任务编排指的是对多个线程任务按照一定的逻辑顺序或条件进行组织和安排,以实现协同工作、顺序执行或并行执行的一种机制。 1.线程任务编排 VS 线程通讯 有同学可能会想:那线程的任务编排是不是问的就是线程间通讯啊? 线程间通讯我知道了,它的实现方式总共有以下几种方式: Object 类下的 wait

美团VS饿了么,到底谁更胜一筹?

最近啊,收到一个粉丝的投稿,我发现他在美团和饿了么都去面试过。 这俩企业大家应该都经常用吧,咱点外卖的时候,我有时候就琢磨,到底他俩谁更厉害点。 今天咱们就瞅瞅,在面试这块儿谁更难一些。 (目前都只有一面的情况,要是想要后续的,私聊我发给你哈) 美团 一面 自我介绍 项目做完了吗?背景是什么?项目初

抢先看!美团、京东、360等大厂面试题解析,技术面试必备。

技术面试必备!美团、京东、360等大厂面试题详解,让你轻松应对各大公司面试挑战! 往期硬核面经 哦耶!冲进腾讯了! 牛逼!上岸腾讯互娱和腾讯TEG! 腾讯的面试,强度拉满! 前几篇文章分享了上岸腾讯的最新面经。 不少粉丝股东留言说别只发腾讯的啦,其他大厂的也安排一些吧,比如美团、360、京东的。 必

美团一面问我i++跟++i的区别是什么

美团一面问我i++跟++i的区别是什么 面试官:“i++跟++i的区别是什么?” 我:“i++是先使用然后再执行+1的操作,++i是先执行+1的操作然后再去使用i” 面试官:“那你看看下面这段代码,运行结果是什么?” public static void main(String[] args) {

美团携手HarmonyOS SDK,开启便捷生活新篇章

华为开发者大会(HDC 2024)于6月21日在东莞松山湖拉开序幕,通过一系列精彩纷呈的主题演讲、峰会、专题论坛和互动体验,为开发者们带来了一场知识与技术的盛宴。6月23日,《HarmonyOS开放能力,使能应用原生易用体验》分论坛成功举办,美团作为鸿蒙原生应用开发中的优秀案例,受邀出席了此次活动。

美团一面:什么是CAS?有什么优缺点?我说我只用过AtomicInteger。。。。

引言 传统的并发控制手段,如使用synchronized关键字或者ReentrantLock等互斥锁机制,虽然能够有效防止资源的竞争冲突,但也可能带来额外的性能开销,如上下文切换、锁竞争导致的线程阻塞等。而此时就出现了一种乐观锁的策略,以其非阻塞、轻量级的特点,在某些场合下能更好地提升并发性能,其中

美团二面:SpringBoot读取配置优先级顺序是什么?

理解并合理运用Spring Boot配置加载的优先级,对于保障应用的安全性、可维护性以及降低部署复杂度至关重要。特别是在大规模微服务架构中,合理的配置管理和迁移对于整体系统的稳定性有着不可忽视的作用。

(转载)自动化测试在美团外卖的实践与落地

(转载)自动化测试在美团外卖的实践与落地 侵删 原文链接: https://tech.meituan.com/2022/09/15/automated-testing-in-meituan.html 美团这个技术博客不少内容都不错的,推荐阅读 1. 项目背景 美团外卖的业务场景比较多元化,除了外卖自