Java 保持';明显的';锁定检索还是使用双重检查锁定?

Java 保持';明显的';锁定检索还是使用双重检查锁定?,java,optimization,concurrency,race-condition,Java,Optimization,Concurrency,Race Condition,我不擅长提出问题。我有以下一段(Java)代码(伪): 基本上,它使标识符“唯一”(引用等于,如=),检查缓存,如果缓存未命中,则调用昂贵的方法(包括调用外部资源和解析等),将其放入缓存并返回。在这种情况下,同步的标识符避免了两个equals(),而不是=标识符对象被用于调用昂贵的方法,该方法将同时检索相同的资源 以上工作。我只是想知道,也许是微观优化,像下面这样采用更原始的缓存检索和双重检查锁定的重写会是“安全的”(在线程安全中是安全的,没有奇怪的竞争条件)还是“更优化的”(在减少不必要的锁定

我不擅长提出问题。我有以下一段(Java)代码(伪):

基本上,它使标识符“唯一”(引用等于,如
=
),检查缓存,如果缓存未命中,则调用昂贵的方法(包括调用外部资源和解析等),将其放入缓存并返回。在这种情况下,同步的
标识符
避免了两个
equals()
,而不是
=
标识符
对象被用于调用昂贵的方法,该方法将同时检索相同的资源

以上工作。我只是想知道,也许是微观优化,像下面这样采用更原始的缓存检索和双重检查锁定的重写会是“安全的”(在线程安全中是安全的,没有奇怪的竞争条件)还是“更优化的”(在减少不必要的锁定和线程必须等待锁定方面)


你可以说“只是测试它”或“只是做一个微基准测试”,但测试多线程代码并不是我的强项,我怀疑我是否能够模拟现实情况或准确地模拟比赛条件。另外,这需要我半天的时间,而写一个SO问题只需要我几分钟:)。

同步最多需要2微秒。除非你需要进一步减少这一点,否则最好用最简单的解决方案

顺便说一句,你可以写

SomeObject cached = cache.get(singletonInstance);
if (cached == null) 
   cache.put(singletonInstance, cached = createSomeObject(singletonInstance));
return cached;
如果“cache”是一个映射(我怀疑它是),那么这个问题与简单的双重检查锁定问题完全不同

如果缓存是一个普通的HashMap,那么问题实际上要严重得多;i、 e.您建议的“双重检查模式”的行为比简单的基于引用的双重检查糟糕得多。事实上,它可能导致ConcurrentModificationException、获取不正确的值,甚至导致无限循环

如果它基于一个普通的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;
}

您正在重塑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);
}
ConcurrentMap cache=new MapMaker().makeComputingMap(new Function()){
公共对象应用(标识符来自){
返回createSomeObject(从);
}
};
公共SomeObject getObject(标识符someIdentifier){
返回cache.get(someIdentifier);
}
此处不需要进行内部填充,因为ComputingMap保证只有一个线程在不存在时才会尝试填充,而另一个请求相同项目的线程将阻止并等待结果。如果删除正在填充的密钥,则该线程和当前正在等待的任何线程仍将获得该结果,但后续请求将再次启动填充


如果您确实需要实习,该库提供了优秀的实习类,该类具有强引用缓存和弱引用缓存。

预先警告:该论文描述了Java5内存模型之前DCL的状态。在新内存模型下,volatile的语义是固定的,以便DCL模式能够正确工作总是倾向于使用封装惰性的til:最初的问题专门询问如何避免双重创建对象。这个答案将从多个线程调用
createSomeObject(singletonInstance)
。基本上,这里需要的是Guava MapMaker.makeComputingMap()的语义。
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;
}
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);
}