如何使用 ConcurrentHashMap 执行线程安全的获取然后删除?
How can I perform a thread-safe get then remove with ConcurrentHashMap?
在一次采访中,我被要求检查以下代码是否按预期工作。
ConcurrentHashMap<Integer, Integer> chm = new ConcurrentHashMap<>();
if (chm.get(key) != null) {
chm.get(key).doSomething();
chm.remove(key);
}
根据 JavaDocs,get
returns 最后完成更新操作的值。因此,如果线程 1 已经调用了 chm.remove(key)
并且如果线程 2 进入了 if 语句并且即将调用 get
方法,那么我们可能会得到一个异常。这是正确的吗?
如何使这个线程安全?
你是对的。如果这个 Map
可以被多个线程修改,那么第一次调用 chm.get(key)
可能会 return 一个非空值,而第二次调用 return [=13] =](由于另一个线程从 Map
中删除了密钥),因此 chm.get(key).doSomething()
将抛出 NullPointerException
.
您可以通过使用局部变量来存储 chm.get(key)
:
的结果来使此代码线程安全
ConcurrentHashMap<Integer, Integer> chm = new ConcurrentHashMap<Integer, Integer>();
Integer value = chm.get(key);
if(value != null) {
value.doSomething(); // P.S. Integer class doesn't have a doSomething() method
// but I guess this is just an example of calling some arbitrary
// instance method
chm.remove(key);
}
顺便说一句,即使 Map
不是 ConcurentHashMap
并且只有一个线程可以访问它,我仍然会使用局部变量,因为它比调用 [= =20=]方法两次。
编辑:
如下所述,此修复不会阻止 doSomething()
被不同线程多次调用相同的 key/value。这是否是所需的行为尚不清楚。
如果您希望防止 doSomething()
被同一个 key/value 的多个线程调用的可能性,您可以使用 chm.remove(key)
来删除键和获取值同样的步骤。
然而,这存在 doSomething()
对于某些 key/value 根本不会执行的风险,因为如果第一次调用 doSomething()
导致异常,则不会另一个线程对 doSomething()
的另一个调用,因为 key/value 对将不再在 Map
中。另一方面,如果仅在 doSomething()
成功执行后才从 Map 中删除 key/value 对,则可以保证 doSomething()
对所有 key/value 至少执行一次从 Map
.
中删除的对
Map.remove(key)
returns 已删除的值。这在很多情况下都是一个很好的技巧,包括你的情况:
Object value = chm.remove(key)
if(value != null)
{
value.doSomething();
}
你不能安全地使用 get 然后 remove,因为如果两个线程同时调用你的方法,他们总是有风险调用 doSomething
两次或更多次,然后键被已删除。
如果先删除它,这是不可能的。上面的代码是线程安全的,也更简单。
在一次采访中,我被要求检查以下代码是否按预期工作。
ConcurrentHashMap<Integer, Integer> chm = new ConcurrentHashMap<>();
if (chm.get(key) != null) {
chm.get(key).doSomething();
chm.remove(key);
}
根据 JavaDocs,get
returns 最后完成更新操作的值。因此,如果线程 1 已经调用了 chm.remove(key)
并且如果线程 2 进入了 if 语句并且即将调用 get
方法,那么我们可能会得到一个异常。这是正确的吗?
如何使这个线程安全?
你是对的。如果这个 Map
可以被多个线程修改,那么第一次调用 chm.get(key)
可能会 return 一个非空值,而第二次调用 return [=13] =](由于另一个线程从 Map
中删除了密钥),因此 chm.get(key).doSomething()
将抛出 NullPointerException
.
您可以通过使用局部变量来存储 chm.get(key)
:
ConcurrentHashMap<Integer, Integer> chm = new ConcurrentHashMap<Integer, Integer>();
Integer value = chm.get(key);
if(value != null) {
value.doSomething(); // P.S. Integer class doesn't have a doSomething() method
// but I guess this is just an example of calling some arbitrary
// instance method
chm.remove(key);
}
顺便说一句,即使 Map
不是 ConcurentHashMap
并且只有一个线程可以访问它,我仍然会使用局部变量,因为它比调用 [= =20=]方法两次。
编辑:
如下所述,此修复不会阻止 doSomething()
被不同线程多次调用相同的 key/value。这是否是所需的行为尚不清楚。
如果您希望防止 doSomething()
被同一个 key/value 的多个线程调用的可能性,您可以使用 chm.remove(key)
来删除键和获取值同样的步骤。
然而,这存在 doSomething()
对于某些 key/value 根本不会执行的风险,因为如果第一次调用 doSomething()
导致异常,则不会另一个线程对 doSomething()
的另一个调用,因为 key/value 对将不再在 Map
中。另一方面,如果仅在 doSomething()
成功执行后才从 Map 中删除 key/value 对,则可以保证 doSomething()
对所有 key/value 至少执行一次从 Map
.
Map.remove(key)
returns 已删除的值。这在很多情况下都是一个很好的技巧,包括你的情况:
Object value = chm.remove(key)
if(value != null)
{
value.doSomething();
}
你不能安全地使用 get 然后 remove,因为如果两个线程同时调用你的方法,他们总是有风险调用 doSomething
两次或更多次,然后键被已删除。
如果先删除它,这是不可能的。上面的代码是线程安全的,也更简单。