假设:
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)
是线程安全的,可能不需要双重检查锁定(但是还不太确定这个......)。但是未来的isDone()
方法呢?synchronized
区块中选择正确的锁定对象?我知道它不能是null
,因此我首先使用TaskLauncher.class
中的getInstance()
对象,然后使用tasksCache
方法中已经初始化的getTask(String key)
。事实上这个选择有什么重要性吗?答案 0 :(得分:2)
我是否需要在getTask方法中执行双重检查锁定控制?
您不需要需要进行双重检查锁定(DCL)。 (实际上,非常罕见你需要使用DCL。在99.9%的情况下,常规锁定就好了。现代JVM上的常规锁定足够快,以至于DCL的性能优势是通常太小而不能产生明显的差异。)
但是,除非您将tasksCache
声明为final
,否则同步是。如果tasksCache
不是final
,那么简单锁定应该没问题。
我知道ConcurrentHashMap对于读取操作是线程安全的......
这不是问题。问题是,如果在不同的线程上创建并使用taskCache
,那么读取TaskLauncher
引用的值是否会为您提供正确的值。从变量中获取引用的线程安全性不会受到引用对象的线程安全性的影响。
但是Future的isDone()方法呢?
再次......这与您是否需要使用DCL或其他同步无关。
对于记录,Future
的内存语义“契约”在javadoc中指定:
“内存一致性影响:异步计算所采取的操作发生在另一个线程中相应的Future.get()之后的操作之前。”
换句话说,当您(正确实施)get()
上调用Future
时,无需额外同步。
如何在同步块中选择正确的锁定对象?
锁定用于同步访问由不同线程读取和写入的变量,同时保持锁定。
理论上,您可以编写整个应用程序以仅使用一个锁。但是如果你这样做,你会得到一个线程等待另一个线程的情况,尽管第一个线程不需要使用另一个线程使用的变量。因此,通常的做法是使用与变量关联的锁。
您需要确保的另一件事是,当两个线程需要访问同一组变量时,它们使用与锁相同的对象(或对象)。如果他们使用不同的锁,那么他们就无法实现正确的同步......
(还有关于锁定this
或私有锁的问题,以及关于应该获取锁定的顺序。但这些超出了您提出的问题的范围。)
这些是一般的“规则”。要在特定情况下做出决定,您需要准确了解您要保护的内容,并相应地选择锁定。
答案 1 :(得分:1)
FutureTask具有变量状态 线程及其易失性(线程安全)变量。所以不必担心isDone()方法。
private volatile int state;
如果所有实例都有自己的共享内存,则使用这个。使用它还可以为您节省一个额外的锁定对象。 在你的情况下你可以使用 TaskLauncher.class,tasksCache,这在singelton的同步方面完全相同。