ConcurrentHashMap 删除问题

ConcurrentHashMap remove issue

我有一个class这样的

import java.util.concurrent.*;
public class TestClass<V> {
    private final ConcurrentMap<String, Future<V>> requests;
    private final ExecutorService executorService;

    public TestClass(final ExecutorService executorService) {
        this.executorService = executorService;
        this.requests = new ConcurrentHashMap<>();
    }

    public V submitRequest(String cacheKey, Callable<V> request) throws Exception {
        final Future<V> task = getOrCreateTask(cacheKey, request);
        final V results;
        try {
            results = task.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new IllegalStateException(String.format("Exception while executing request for key '%s'", cacheKey),
                    e);
        } finally {
            //Nullpointer here
            requests.remove(cacheKey);
        }

        return results;
    }


    private synchronized Future<V> getOrCreateTask(String key, Callable<V> request) {
        if (requests.containsKey(key)) {

            return requests.get(key);
        } else {

            final Future<V> newTask = executorService.submit(request);
            requests.put(key, newTask);
            return newTask;
        }


       }
    }

但有时在重负载下服务器会在 requests.remove(cacheKey) 上抛出空指针。我读过 final when not escape by this in the constructor is write guaranteed。即其他线程可以看到我的请求映射发生了什么。

不确定如何有效修复?不喜欢在整个父级方法上添加同步的想法

我不确定 NPE 是否在您识别它的位置,除非 cacheKey 为 null,您可以检查一下。 concurrentmap 设置正确,因此 requests 字段永远不应该为 null。然而,此代码未正确同步。您正尝试在 getOrCreateTask() 中执行两个操作,虽然在 synchronized 关键字下未与地图正确同步,因为当您删除值时地图在 submitRequest 中进行交互。

可能发生的情况是,在检查 ConcurrentMap#containsKey 和 ConcurrentMap#get 之间,另一个线程已从缓存中删除该值 (ConcurrentMap#remove)。

  • 线程 A:检查包含 "foobar" => true
  • 线程 B:删除 "foobar"
  • 线程 A:调用 get("foobar") => null
  • 线程 A:在空指针上调用 Future#get,然后抛出 NPE。

由于您控制并发映射,您可以知道您永远不会有空值。在这种情况下,您应该只调用#get 方法并检查返回值是否为空。这将防止另一个线程删除 contains/get 对之间的值,因为您将仅通过一个原子操作访问地图。