Java 单例实例实例化

Java 单例实例实例化,java,static,synchronization,singleton,volatile,Java,Static,Synchronization,Singleton,Volatile,我开始习惯Java中的关键字static和volatile。关于我正在构建的一个单身类,为什么我会看到以下设计 public class Singleton{ private static volatile Singleton _instance; public static Singleton getInstance(){ if(_instance == null){ synchronized(Singleton.class){

我开始习惯Java中的关键字
static
volatile
。关于我正在构建的一个单身类,为什么我会看到以下设计

public class Singleton{
    private static volatile Singleton _instance; 

    public static Singleton getInstance(){

       if(_instance == null){
           synchronized(Singleton.class){
               if(_instance == null)
                   _instance = new Singleton();
           }

       }
       return _instance;

    }
}
而不是这个

public class Singleton{ 
    private static volatile Singleton _instance = new Singleton();

    public static Singleton getInstance(){
        return _instance;
    }
}

第一种设计比第二种有什么优势吗?我能看到的唯一优点是第二个设计同步了整个方法,这可能需要阻塞整个方法。但我的反驳是,这个方法只有一行,不会明显阻塞,对吗

实际上,第二个类执行“惰性”初始化,因此,如果实例化类可能需要很多时间,并且您不确定应用程序中是否需要该类的实例,则可以使用。在这种情况下,您可以“按需”创建一个实例。
根据多线程环境-这两种设计都是线程安全的。

第一个示例对于多线程应用程序很有用,但被证明是不安全的,除非您使用
volatile
关键字。它被称为双重检查锁定单例模式

这个想法是: -检查对象是否为空,因此如果不返回,请避免锁定。 -如果
null
,则锁定该类。 -再次检查它是否为
null
,因为其他线程可能在当前线程之前锁定了它。 -如果仍然为null,则实例化它

使用volatile可以解决这个问题,因为它可以确保其他线程在前一个线程完成之前不会读取变量,但是,锁定和同步操作在CPU时间方面是非常昂贵的

正确的方法是使用Bill Pugh的解决方案:

public class Singleton {
    // Private constructor prevents instantiation from other classes
    private Singleton() { }

    /**
    * SingletonHolder is loaded on the first execution of Singleton.getInstance() 
    * or the first access to SingletonHolder.INSTANCE, not before.
    */
    private static class SingletonHolder { 
            public static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
            return SingletonHolder.INSTANCE;
    }
}
它是线程安全的,并且是正确的方法。这也是一个延迟初始化,因为内部类只有在被引用时才被加载,static关键字确保每个类只有一个实例


您的第二个示例可以工作,但它不是惰性初始化的。

我更愿意添加注释,但似乎还不允许我这么做

第一个示例是双重检查锁定,它在Java中不再被破坏:

从J2SE5.0开始,此问题已得到修复。volatile关键字现在可以确保多个线程正确处理单实例。 ~

双重检查锁定的思想是在需要时才创建单例对象,并尽可能少地使用synchronized锁定对象

第二个示例将在类加载后(可能是在启动时)创建singleton,而不管是否使用过它


这两个示例都是线程安全的,并且从J2SE5.0开始完全可用。这取决于您是否愿意承担创建singleton对象的成本,以及是否可以在内存中保存一个从未使用过的对象。

什么是“明显阻塞”呢?仅仅因为我们注意不到汇编语言的指令时间并不意味着它们在现实生活中无关紧要。我很确定第一个例子不需要两次检查空实例。因此,第二个检查可以删除。在这个类中,实例无法从new'd变为null。@user619818第二次检查是必要的,因为自上次检查以来,另一个线程可能已经初始化了实例。啊,如果在线程之间进行上下文切换。好的。@DaveNewton当我说“明显阻塞”时,我的意思是从每个设计调用每个getInstance()方法所需的时间没有差别。为什么不需要将实例变量指定为volatile?是否每个线程都有自己的实例缓存并且可能会过时?
volatile
确保其他线程在前一个线程完成之前不会读取它。因此,如果不将实例设置为volatile,难道两个线程不可能同时修改实例并覆盖另一个实例吗?这篇文章是从2001年开始的,在1.5+下不再有效,这是否是最好的方法是另一个问题。@Darijan Well,no.1。声明field final可确保不存在可见性问题。2.即使您没有声明它为final,但实际上是不可变的(在本例中是不变的),因为它是以内联方式初始化为静态字段的,它也不会有可见性问题