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 对之间的值,因为您将仅通过一个原子操作访问地图。
我有一个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 对之间的值,因为您将仅通过一个原子操作访问地图。