周末的时候,有一位小伙伴提了一些关于 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 为何可以?的更多相关文章

  1. java 美团面试常见问题总

    一 基础篇 1. System.out.println(3|9)输出什么? 2. 说一下转发(Forward)和重定向(Redirect)的区别 3. 在浏览器中输入url地址到显示主页的过程,整个过 ...

  2. Java 常用数据结构对象的实现原理 集合类 List Set Map 哪些线程安全 (美团面试题目)

    Java中的集合包括三大类,它们是Set.List和Map, 它们都处于java.util包中,Set.List和Map都是接口,它们有各自的实现类. List.Set都继承自Collection接口 ...

  3. 面试题:对NotNull字段插入Null值 有啥现象?

    Hi,大家好!我是白日梦. 今天我要跟你分享的话题是:"对NotNull字段插入Null值有啥现象?" 一. 推荐阅读 首发地址:https://mp.weixin.qq.com/ ...

  4. 助力面试之ConcurrentHashMap面试灵魂拷问,你能扛多久

    目录 前言 ConcurrentHashMap 原理 JDK1.8 版本 ConcurrentHashMap 做了什么改进 为什么 key 和 value 不允许为 null ConcurrentHa ...

  5. MySQL timestamp NOT NULL插入NULL的问题

    explicit_defaults_for_timestamp MySQL 5.6版本引入 explicit_defaults_for_timestamp 来控制对timestamp NULL值的处理 ...

  6. Ibatis insert语句插入null引发的错误

    公司使用的orm框架为ibatis,其中默认的insert语句一直都是这样写的: <insert id="insert" parameterClass="activ ...

  7. FDQuery 怎么能插入NULL参数

    [FireDAC][Phys][MSSQL]-335. Parameter [fieldAA] data type is unknown. Hint: specify TFDParam.DataTyp ...

  8. kettle 数据抽取时会出现 无法插入NULL

    kettle 数据抽取时会出现 无法插入NULL,其实是空字符串,原因是kettle默认不区分空字符串和NULL. 解决办法: 修改kettle.properties 文件:

  9. 【Java基础】ConcurrentHashMap为什么不能存null键和null值

    代码如下 /** * 测试ConcurrentHashMap null键和null值的问题 * @return */ @RequestMapping(value = "/get_nacos& ...

  10. 面试一次问一次,HashMap是该拿下了(一)

    文章目录 前言 一.HashMap类图 二.源码剖析 1. HashMap(jdk1.7版本) - 此篇详解 2. HashMap(jdk1.8版本) 3. ConcurrentHashMap ~~ ...

随机推荐

  1. uni-app 运行项目

    运行-运行到浏览器-选择浏览器 运行新项目:真机测试需要打开真机的开发者选项usb安装调试功能

  2. drf——Request源码分析、序列化组件、序列化类的使用(字段类和参数)、反序列化校验和保存

    1.Request类源码分析 # APIView+Response写个接口 # 总结: 1.新的request有个data属性,以后只要是在请求body体中的数据,无论什么编码格式,无论什么请求方式 ...

  3. [安全开发] SQL注入扫描(一股子GPT味~)

    实际上大部分都是它写的,它真我哭 SQL注入扫描就是一种用于检测和预防SQL注入攻击的工具.它通过模拟SQL注入攻击的方式,向目标网站发送特定的SQL查询语句,以验证目标网站是否存在SQL注入漏洞.S ...

  4. L2-001 紧急救援 (25 分)

    1.题目描述: 作为一个城市的应急救援队伍的负责人,你有一张特殊的全国地图.在地图上显示有多个分散的城市和一些连接城市的快速道路.每个城市的救援队数量和每一条连接两个城市的快速道路长度都标在地图上.当 ...

  5. idea过期解决

    用作用作发现过期了,苦恼,好办直接 搞个code 就行 MNQ043JMTU-eyJsaWNlbnNlSWQiOiJNTlEwNDNKTVRVIiwibGljZW5zZWVOYW1lIjoiR1VPI ...

  6. 【最佳实践】如何设计 RESTful API ?

    良好的 API 设计是一个经常被提及的话题,特别是对于那些试图完善其 API 策略的团队来说.一个设计良好的 API 的好处包括:改进开发者体验.更快速地编写文档以及更高效地推广你的 API.但是,到 ...

  7. 配置VsCode的QT工程

    配置VsCode的QT工程 VsCode + qmake 环境(Environment): Windows11 Qt5.12.11+ MinGW64 编译套件 VsCode (version = 1. ...

  8. bugku xxx二手交易市场

    打开靶场 分析 很明显需要先注册一个账号才行 完成后发现两个上传图片的地方 一个是更换头像, 一个是发布内容 先更换头像试试 首先上传一个木马图片,会发现一直转圈圈,(卡住了) 只能先上传正常的图片了 ...

  9. C/S架构和B/S架构两种数字孪生技术路线的区别是什么?

    山海鲸创造了一种CS和BS热切换的编辑模式,即CSaaS架构,可以在安装软件之后一键从软件的CS状态切换为一个BS服务器,让私有化部署变得十分轻松.具体效果可以参照下面的视频: (https://ww ...

  10. 【后端面经-Java】Java创建线程的方法简介

    目录 1. 线程的基本概念 1.1 线程 1.2 线程状态和生命周期 2. 创建线程的四种方法 2.1 继承Thread类 2.2 实现Runnable接口 2.3 实现Callable接口 2.4 ...