- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
周末的时候,有一位小伙伴提了一些关于 ConcurrentHashMap 的问题,都是他最近面试遇到的。原提问如下:
整个提问看着非常复杂,其实归纳来说就是两个问题:
ConcurrentHashMap
为什么 key 和 value 不能为 null? ConcurrentHashMap
能保证复合操作的原子性吗? 下面我会以此提供这两个问题的详细答案,希望对你有帮助.
ConcurrentHashMap 的 key 和 value 不能为 null 主要是为了避免二义性。null 是一个特殊的值,表示没有对象或没有引用。如果你用 null 作为键,那么你就无法区分这个键是否存在于 ConcurrentHashMap 中,还是根本没有这个键。同样,如果你用 null 作为值,那么你就无法区分这个值是否是真正存储在 ConcurrentHashMap 中的,还是因为找不到对应的键而返回的.
拿 get 方法取值来说,返回的结果为 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 是线程安全的,意味着它可以保证多个线程同时对它进行读写操作时,不会出现数据不一致的情况,也不会导致 JDK1.7 及之前版本的 HashMap 多线程操作导致死循环问题。但是,这并不意味着它可以保证所有的复合操作都是原子性的,一定不要搞混了! 。
复合操作是指由多个基本操作(如 put 、 get 、 remove 、 containsKey 等)组成的操作,例如先判断某个键是否存在 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 的执行顺序是这样:
那么最终的结果是 (key, value),而不是预期的 (key, anotherValue)。这就是复合操作的非原子性导致的问题.
那如何保证 ConcurrentHashMap 复合操作的原子性呢?
ConcurrentHashMap 提供了一些原子性的复合操作,如 putIfAbsent 、 compute 、 computeIfAbsent 、 computeIfPresent 、 merge 等。这些方法都可以接受一个函数作为参数,根据给定的 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为何可以?的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我有很多观点想要与另一种观点进行交流。我们将另一个 View 称为“主视图”。我想要做的是让“许多其他 View ”能够向“主视图”发送添加 subview 的方法。我会创建一个委托(delegate
在 Smalltalk 中(更具体地说在 Pharo 中)进行委派的最佳方式是什么?我知道 doesNotUnderstand 策略,但它不会委托(delegate) subclassResponsa
我的问题 是否有一个有效的算法来找到最大权重(或最小权重)k- clique在一个完整的 k-partite 图中(根据 wikipedia,顶点相邻当且仅当它们属于不同的 partite 集时)?
我是一名优秀的程序员,十分优秀!