java如何使用ConcurrentHashMap执行线程安全获取然后删除?
在一次采访中,我被要求检查以下代码是否按预期工作
ConcurrentHashMap<Integer, Integer> chm = new ConcurrentHashMap<>();
if (chm.get(key) != null) {
chm.get(key).doSomething();
chm.remove(key);
}
根据JavaDocs,get
返回上次完成的更新操作的值。因此,如果线程1已经调用了chm.remove(key)
,如果线程2在if语句中,并且即将调用get
方法,那么我们可能会得到一个异常。对吗
我怎样才能保证这根线的安全
# 1 楼答案
Map.remove(key)
返回已删除的值。这在很多情况下都是一个很好的技巧,包括你的:使用get-then-remove无法安全地工作,因为如果两个线程同时调用您的方法,那么在密钥被移除之前,它们总是有调用
doSomething
两次或更多次的风险如果先将其移除,则无法执行此操作。上面的代码是线程安全的,也更简单
# 2 楼答案
你说得对。如果这个
Map
可以被多个线程修改,那么对chm.get(key)
的第一个调用可能会返回非空值,第二个调用将返回null
(由于另一个线程从Map
中删除了密钥),因此chm.get(key).doSomething()
将抛出NullPointerException
通过使用局部变量存储
chm.get(key)
的结果,可以使此代码线程安全:顺便说一句,即使
Map
不是ConcurentHashMap
并且只有一个线程可以访问它,我仍然会使用局部变量,因为它比两次调用get()
方法更有效编辑:
如下文所述,此修复程序不会阻止不同线程对同一密钥/值多次调用
doSomething()
。目前尚不清楚这是否是人们想要的行为如果希望防止多个线程为同一个键/值调用
doSomething()
,可以使用chm.remove(key)
在同一步移除键和获取值然而,这样做的风险是,对于某些键/值,
doSomething()
将根本不会执行,因为如果对doSomething()
的第一次调用导致异常,则不会有另一个线程对doSomething()
的另一次调用,因为键/值对将不再位于Map
中。另一方面,如果仅在doSomething()
成功执行后才从映射中删除键/值对,则可以保证对从Map
重新加载的所有键/值对doSomething()
至少成功执行一次