我很狡猾地提出问题。我有以下(Java)代码(伪):
public SomeObject getObject(Identifier someIdentifier) {
// getUniqueIdentifier retrieves a singleton instance of the identifier object,
// to prevent two Identifiers that are equals() but not == (reference equals) in the system.
Identifier singletonInstance = getUniqueIdentifier(someIdentifier);
synchronized (singletonInstance) {
SomeObject cached = cache.get(singletonInstance);
if (cached != null) {
return cached;
} else {
SomeObject newInstance = createSomeObject(singletonInstance);
cache.put(singletonInstance, newInstance);
return newInstance;
}
}
}
基本上,它使标识符'唯一'(引用等于,如在==
中),检查缓存,并且在高速缓存未命中的情况下,调用昂贵的方法(涉及调用外部资源和解析,等),将其放入缓存中,然后返回。在这种情况下,同步Identifier
会避免使用两个equals()
但不会==
Identifier
个对象来调用昂贵的方法,这会同时检索相同的资源。
以上作品。我只是想知道,并且可能是微优化,重写如下,采用更天真的缓存检索和双重检查锁定是'安全的'(在线程安全中是安全的,没有奇怪的竞争条件)并且是'更多最佳'(如减少不需要的锁定和线程必须等待锁定)?
public SomeObject getObject(Identifier someIdentifier) {
// just check the cache, reference equality is not relevant just yet.
SomeObject cached = cache.get(someIdentifier);
if (cached != null) {
return cached;
}
Identifier singletonInstance = getUniqueIdentifier(someIdentifier);
synchronized (singletonInstance) {
// re-check the cache here, in case of a context switch in between the
// cache check and the opening of the synchronized block.
SomeObject cached = cache.get(singletonInstance);
if (cached != null) {
return cached;
} else {
SomeObject newInstance = createSomeObject(singletonInstance);
cache.put(singletonInstance, newInstance);
return newInstance;
}
}
}
你可以说“只是测试它”或“只做一个微基准测试”,但测试多线程代码不是我的强项,我怀疑我能够模拟真实情况或准确假的竞争条件。加上它需要我半天,而写一个SO问题只需要几分钟:)。
答案 0 :(得分:0)
同步最多需要2微秒。除非你需要进一步削减,否则最简单的解决方案可能会更好。
BTW你可以写
SomeObject cached = cache.get(singletonInstance);
if (cached == null)
cache.put(singletonInstance, cached = createSomeObject(singletonInstance));
return cached;
答案 1 :(得分:0)
如果“cache”是一个地图(我怀疑它是),那么这个问题与简单的双重检查锁定问题完全不同。
如果缓存是普通的HashMap,那么问题实际上要糟糕得多;即你提出的“双重检查模式”比简单的基于参考的双重检查更糟糕。实际上,它可能导致ConcurrentModificationExceptions,获取不正确的值,甚至是无限循环。
如果它基于普通的HashMap,我建议使用ConcurrentHashMap作为第一种方法。使用ConcurrentHashMap,您无需显式锁定。
public SomeObject getObject(Identifier someIdentifier) {
// cache is a ConcurrentHashMap
// just check the cache, reference equality is not relevant just yet.
SomeObject cached = cache.get(someIdentifier);
if (cached != null) {
return cached;
}
Identifier singletonInstance = getUniqueIdentifier(someIdentifier);
SomeObject newInstance = createSomeObject(singletonInstance);
SombObject old = cache.putIfAbsent(singletonInstance, newInstance);
if (old != null) {
newInstance = old;
}
return newInstance;
}
答案 2 :(得分:0)
您正在重塑Google-Collections / Guava的MapMaker / ComputingMap:
ConcurrentMap<Identifier, SomeObject> cache = new MapMaker().makeComputingMap(new Function<Identifier, SomeObject>() {
public SomeObject apply(Identifier from) {
return createSomeObject(from);
}
};
public SomeObject getObject(Identifier someIdentifier) {
return cache.get(someIdentifier);
}
此处不需要实习,因为ComputingMap保证单个线程只会在缺席时尝试填充,而另一个要求同一项的线程将阻塞并等待结果。如果删除正在填充的密钥,那么该线程和当前正在等待的任何密钥仍将获得该结果,但后续请求将再次启动填充。
如果你确实需要实习,那么该库提供了优秀的Interner类,它具有强引用和弱引用的缓存。