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

时间:2015-12-09 17:06:19

标签: 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();
    }
}

public static DataClient getInstance() {
    if (instance == null) {
        initialize();
    }
    return instance;
}

这就是我使用它的方式:

DataClient.getInstance();

这个线程是否安全,如果没有,那么任何人都可以解释为什么它不是线程安全的吗?

2 个答案:

答案 0 :(得分:4)

  

这个线程是否安全,如果没有,那么任何人都可以解释为什么它不是线程安全的吗?

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

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

以下是一些使其安全的方法:

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

    protected static final DataClient instance = new DataClient();
    
  • 您可以使用AtomicReference,但只包含volatile个对象,以便instance volatile类似。

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

例如,假设您有2个主题。 Thread1调用getInstance()instancenull。它调用initialize()构建DataClient并将其发布到static instance变量。然后它到达synchronized块的末尾,因此instance被发布到中央存储器。

Thread2现在调用getInstance()并获取对instance的引用,但是instance内存的某些部分已缓存,现在已过时。因为它没有跨越读取内存屏障,所以没有强制Thread2更新其内存缓存以确保instance已正确共享的机制。它可能会看到instance只更新了一些字段或没有更新字段。内存同步必须由发布者和消费者线程 应用,否则可能会发生内存竞争条件。

答案 1 :(得分:-3)

应该是,Race Conditions还能保证“保持线程安全”的一般想法吗?如果是这样的话,我认为这不会成为一个问题,因为它可能会破坏你对其他代码的排序。

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