Java 如何编写线程安全的getInstance方法?

Java 如何编写线程安全的getInstance方法?,java,multithreading,thread-safety,locking,Java,Multithreading,Thread Safety,Locking,我的库中有一个getInstance方法,用于多个线程,但我不确定它是否是线程安全的: protected static DataClient instance; protected DataClient() { // do stuff here } public static synchronized void initialize() { if (instance == null) { instance = new DataClient(); }

我的库中有一个
getInstance
方法,用于多个线程,但我不确定它是否是线程安全的:

protected static DataClient instance;

protected DataClient() {
    // do stuff here
}

public static synchronized void initialize() {
    if (instance == null) {
        instance = new DataClient();
    }
}

public static DataClient getInstance() {
    if (instance == null) {
        initialize();
    }
    return instance;
}
这就是我使用它的方式:

DataClient.getInstance();

这是线程安全的吗?如果不是,那么有人能解释为什么它不是线程安全的吗?

应该是这样的,让某些东西“线程安全”的一般想法不也是有保证的吗?如果是这样的话,我认为在调用它时这不会是一个问题,因为它可能会打乱您对其他代码的排序

编辑:基本上,因为您只读取实例,所以这不应该是一个问题,因为您没有在这个方法中对实例执行其他操作

这是线程安全的吗?如果不是,那么有人能解释为什么它不是线程安全的吗

它不是线程安全的,因为您实际上正在执行错误。没有任何东西可以保护
实例在完全实例化之前不被发布。仅仅因为
initialize()
synchronized
并不意味着它不会在同步块结束之前发布
instance
,这可以在非同步
getInstance()
方法中看到。因此,调用
getInstance()
的线程可能会获得对部分初始化的
DataClient
实例的引用

另外,即使创建线程发布它,也不能保证另一个线程会更新与该对象关联的内存。缓存内存可能会导致部分对象和其他关键内存问题

以下是一些安全的方法:

  • 最简单的方法是将
    实例
    设置为
    易失性
    。这将强制在写入
    实例
    时跨越写入内存屏障,在访问实例时跨越读取内存屏障。这修复了双重检查锁定错误
  • 您可以在同步加载类时实例化对象:

    protected static final DataClient instance = new DataClient();
    
  • 您可以使用
    AtomicReference
    ,但它只是包装了
    volatile
    对象,因此使
    实例
    volatile
    类似

  • 您可以确保
    DataClient
    中的所有字段都是
    final
    volatile
    ,以便在构造函数完成时完全构造对象。尽管仍然是一种糟糕的模式,但我相信这会阻止内存模型在构建对象后重新排序构造函数初始化。更多

例如,假设您有两个线程。Thread1调用
getInstance()
,其中
instance
null
。它调用
initialize()
,该函数构造
DataClient
,并将其发布到
static
实例
变量中。然后它到达
synchronized
块的末尾,因此
实例
被发布到中央内存

Thread2现在调用
getInstance()
并获取对
instance
的引用,但缓存了
instance
内存的某些部分,这些部分现在已过期。由于Thread2不跨越读取内存障碍,因此没有强制Thread2更新其内存缓存的机制,以确保
实例
已正确共享。它可能会看到
实例
,其中只有部分字段或没有更新。发布者和使用者线程都必须应用内存同步,否则可能出现内存争用情况



您之前询问并删除了此问题。如果我没有浪费精力回答前面的问题,那么我可能更倾向于在这个迭代中编写一个答案。两个线程可以同时看到
if(instance==null)
,然后初始化实例。这通常被认为是不安全的,因为其中一个线程将以错误的实例结束。很公平,我没有想到这一点。哦,有一个已同步的线程,所以这不是我所想的,但好的老线程解释了它仍然可能失败的丑陋细节。