如何使用单例方法以更好的方式实现同​​步?

时间:2017-07-11 03:59:33

标签: java multithreading synchronization thread-safety singleton

使用Singleton Pattern实现的类如下所示,当多个线程访问此方法时,只有一个线程必须创建实例,所以我所做的就是同步方法

private static synchronized FactoryAPI getIOInstance(){
    if(factoryAPI == null){
        FileUtils.initWrapperProp();
        factoryAPI =  new FactoryAPIImpl();
    }
    return factoryAPI;
}

我觉得这是不必要的,因为只有第一次创建实例时,其余时间才会返回已创建的实例。将synchronised添加到块时,一次只允许一个线程访问该方法。

getIOInstance执行两项工作

i)初始化属性和

ii)第一次创建新实例

所以,我尝试在此处阻止级别synchronisation,如下所示

private static FactoryAPI getIOInstance(){
    if(factoryAPI == null){
        synchronised {
            if(factoryAPI == null){
                FileUtils.initWrapperProp();
                factoryAPI =  new FactoryAPIImpl();
             }
        }
    }
    return factoryAPI;
}

我更喜欢第二个是正确的。我是以正确的方式使用它吗?欢迎任何建议。

3 个答案:

答案 0 :(得分:1)

使用第一种方法,因为第二种方法不是线程安全的。

当你说,

factoryAPI = new FactoryAPIImpl();

编译器可以按以下顺序自由执行代码:

1)在堆上分配一些内存
2)将factoryAPI初始化为该分配空间的地址
3)调用FactoryAPIImpl的构造函数

问题是当另一个线程在步骤2之后和步骤3之前调用getIOInstance()时。它可能会看到一个指向未初始化的FactoryAPI实例的非null factoryAPI变量。

答案 1 :(得分:0)

这个问题有很多不同的答案,你可以在SEI找到一个广泛的讨论。

现代Java解决方案很简单:使用enum - 因为JLS保证编译器/ JVM将完全创建一个事物。

答案 2 :(得分:0)

发现Initialization-on-demand holder初始化方法是一个有趣的方法,如下所示,

public class FactoryAPI {
    private FactoryAPI() {}

    private static class LazyHolder {
        static final Something INSTANCE = new Something();
    }

    public static Something getInstance() {
        return FactoryAPI.INSTANCE;
    }
}

由于JLS保证类初始化阶段是串行的,即非并发的,因此在加载和初始化期间静态getInstance方法不需要进一步的同步。

由于初始化阶段在一个串行操作中写入静态变量INSTANCEgetInstance的所有后续并发调用将返回相同的正确初始化INSTANCE,而不会产生任何额外的同步开销。