有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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方法,那么我们可能会得到一个异常。对吗

我怎样才能保证这根线的安全


共 (2) 个答案

  1. # 1 楼答案

    Map.remove(key)返回已删除的值。这在很多情况下都是一个很好的技巧,包括你的:

    Object value = chm.remove(key)
    if(value != null)
    {
         value.doSomething();
    }
    

    使用get-then-remove无法安全地工作,因为如果两个线程同时调用您的方法,那么在密钥被移除之前,它们总是有调用doSomething两次或更多次的风险

    如果先将其移除,则无法执行此操作。上面的代码是线程安全的,也更简单

  2. # 2 楼答案

    你说得对。如果这个Map可以被多个线程修改,那么对chm.get(key)的第一个调用可能会返回非空值,第二个调用将返回null(由于另一个线程从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并且只有一个线程可以访问它,我仍然会使用局部变量,因为它比两次调用get()方法更有效

    编辑:

    如下文所述,此修复程序不会阻止不同线程对同一密钥/值多次调用doSomething()。目前尚不清楚这是否是人们想要的行为

    如果希望防止多个线程为同一个键/值调用doSomething(),可以使用chm.remove(key)在同一步移除键和获取值

    然而,这样做的风险是,对于某些键/值,doSomething()将根本不会执行,因为如果对doSomething()的第一次调用导致异常,则不会有另一个线程对doSomething()的另一次调用,因为键/值对将不再位于Map中。另一方面,如果仅在doSomething()成功执行后才从映射中删除键/值对,则可以保证对从Map重新加载的所有键/值对doSomething()至少成功执行一次