【发布时间】:2014-02-14 09:02:17
【问题描述】:
给定:
- 一个惰性初始化的单例类,使用双重检查锁定模式实现,所有相关的
volatile和synchronized都在getInstance中。这个单例通过ExecutorService启动异步操作, - 有七种类型的任务,每一种都由一个唯一的键标识,
- 当一个任务启动时,它被存储在一个基于
ConcurrentHashMap的缓存中, - 当客户端请求任务时,如果缓存中的任务完成,则启动并缓存新的任务;如果它正在运行,则从缓存中检索任务并将其传递给客户端。
这里是代码的摘录:
private static volatile TaskLauncher instance;
private ExecutorService threadPool;
private ConcurrentHashMap<String, Future<Object>> tasksCache;
private TaskLauncher() {
threadPool = Executors.newFixedThreadPool(7);
tasksCache = new ConcurrentHashMap<String, Future<Object>>();
}
public static TaskLauncher getInstance() {
if (instance == null) {
synchronized (TaskLauncher.class) {
if (instance == null) {
instance = TaskLauncher();
}
}
}
return instance;
}
public Future<Object> getTask(String key) {
Future<Object> expectedTask = tasksCache.get(key);
if (expectedTask == null || expectedTask.isDone()) {
synchronized (tasksCache) {
if (expectedTask == null || expectedTask.isDone()) {
// Make some stuff to create a new task
expectedTask = [...];
threadPool.execute(expectedTask);
taskCache.put(key, expectedTask);
}
}
}
return expectedTask;
}
我有一个大问题,还有一个小问题:
- 我是否需要在我的
getTask方法中执行双重检查锁定控制?我知道ConcurrentHashMap对读操作来说是线程安全的,所以我的get(key)是线程安全的,可能不需要双重检查锁定(但对此非常不确定……)。但是Future 的isDone()方法呢? - 如何在
synchronized块中选择正确的锁定对象?我知道它一定不是null,所以我首先在getInstance()中使用TaskLauncher.class对象,然后在getTask(String key)方法中使用已经初始化的tasksCache。事实上,这个选择有什么重要性吗?
【问题讨论】:
-
我在
getTask中看到了一种潜在的竞争条件。您可以启动两个或多个相同的任务,因为您没有检查同步块内缓存中的内容。您需要在同步块中提取新的缓存值,否则双重检查将不起作用。 -
我的意思是
ConcurrentHashMap的线程安全与你是否需要使用DCL无关。这就是您在 Q1 中提出的问题。
标签: java synchronized future concurrenthashmap double-checked-locking