Java 单例和同步

请澄清我关于单例和多线程的疑问:

  • 在多线程环境中,在 Java 中实现 Singleton 的最佳方法是什么?
  • 当多个线程尝试同时访问方法时会发生什么情况?getInstance()
  • 我们可以做单例吗?getInstance()synchronized
  • 使用单例类时,是否真的需要同步?

答案 1

是的,这是必要的。有几种方法可以通过延迟初始化实现线程安全:

严酷同步:

private static YourObject instance;

public static synchronized YourObject getInstance() {
    if (instance == null) {
        instance = new YourObject();
    }
    return instance;
}

此解决方案要求每个线程都同步,而实际上只有前几个线程需要同步。

双重检查同步

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

此解决方案可确保只有尝试获取单例的前几个线程必须经历获取锁的过程。

按需初始化

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

此解决方案利用 Java 内存模型对类初始化的保证来确保线程安全。每个类只能装入一次,并且只会在需要时装入。这意味着第一次被调用,将被加载并创建,并且由于这是由 s 控制的,因此不需要额外的同步。getInstanceInstanceHolderinstanceClassLoader


答案 2

此模式在没有显式同步的情况下对实例进行线程安全的延迟初始化!

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}

它之所以有效,是因为它使用类装入器免费为您执行所有同步:首先在方法内部访问类,因此首次调用类时将加载。此外,类装入器保证在访问类之前完成所有静态初始化 - 这就是为您提供线程安全的原因。MySingleton.LoadergetInstance()LoadergetInstance()

这就像魔术一样。

它实际上与Jhurtado的枚举模式非常相似,但我发现枚举模式滥用了枚举概念(尽管它确实有效)


推荐