非易失性场的出版和读取

2022-09-04 20:49:34
public class Factory {
    private Singleton instance;
    public Singleton getInstance() {
        Singleton res = instance;
        if (res == null) {
            synchronized (this) {
                res = instance;
                if (res == null) {
                    res = new Singleton();
                    instance = res;
                }
            }
        }
        return res;
    }
}

这几乎是线程安全的正确实现。我看到的唯一问题是:Singleton

初始化字段的那个可以在完全初始化之前发布。现在,第二个线程可以在不一致的状态下读取。thread #1instanceinstance

但是,对于我的眼睛来说,这只是这里的问题。这里只有问题吗?(我们可以使不稳定)。instance


答案 1

Shipilev 在 Safe Publication 和 Safe 初始化 in Java 中解释了您的示例。我强烈建议阅读整篇文章,但要总结一下,看看那里的部分:UnsafeLocalDCLFactory

public class UnsafeLocalDCLFactory implements Factory {
  private Singleton instance; // deliberately non-volatile

  @Override
  public Singleton getInstance() {
    Singleton res = instance;
    if (res == null) {
      synchronized (this) {
        res = instance;
        if (res == null) {
           res = new Singleton();
           instance = res;
        }
      }
    }
    return res;
  }
}

以上有以下问题:

此处引入局部变量是一个正确性修复,但只是部分修复:在发布 Singleton 实例和读取其任何字段之间仍然没有发生任何情况。我们只是在保护自己不返回“null”而不是 Singleton 实例。同样的技巧也可以被视为SafeDCLFactory的性能优化,即只执行一次易失性读取,产生:

Shipilev建议通过标记易失性来修复如下:instance

public class SafeLocalDCLFactory implements Factory {
  private volatile Singleton instance;

  @Override
  public Singleton getInstance() {
    Singleton res = instance;
    if (res == null) {
      synchronized (this) {
        res = instance;
        if (res == null) {
          res = new Singleton();
          instance = res;
        }
      }
    }
    return res;
  }
}

此示例没有其他问题。


答案 2

通常情况下,我再也不会使用经过双重检查的锁定机制。要创建线程安全单例,您应该让编译器执行以下操作:

public class Factory {
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return res;
    }
}

现在,您正在谈论使实例易失性。我不认为这个解决方案是必要的,因为jit编译器现在在构造对象时处理线程的同步。但是如果你想让它不稳定,你可以。

最后,我将 getInstance() 和实例设为静态。然后,您可以直接引用 Factory.getInstance() 而无需构造 Factory 类。另外:您将在应用程序中的所有线程上获得相同的实例。否则,每个新的 Factory() 都会给你一个新的实例。

你也可以看看维基百科。如果您需要一个懒惰的解决方案,他们有一个干净的解决方案:

https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java

// Correct lazy initialization in Java
class Foo {
    private static class HelperHolder {
       public static final Helper helper = new Helper();
    }

    public static Helper getHelper() {
        return HelperHolder.helper;
    }
}

推荐