关于单例模式

菩提旭光
  • 2.8k
public class Singleton {

    private static Singleton singleton;

    private Singleton(){
    }

    public static Singleton getInstance() {
        if (null == singleton) {
            singleton = new Singleton();
        }
        return singleton;
    }

}

代码一在类创建的同时就并没有建好一个静态的对象供系统使用,并且线程不安全。

public class Singleton {

    private static Singleton singleton;

    private Singleton(){
    }

    public static Singleton getsychronizedInstance() {
        if (null == singleton) {
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }

}

代码二在类创建的同时就并没有建好一个静态的对象供系统使用,但是线程安全。

public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton(){
    }

    public static Singleton getsychronizedInstance() {
        return singleton;
    }

}

代码三在类创建的同时就已经建好一个静态的对象供系统使用,因此也线程安全。

问题:
饿汉模式和懒汉模式是从线程安全角度来区分?还是从初始化类的时候是否实例化静态对象?来区分的。另外在英文版的设计模式中懒汉模式和饿汉模式对应的英文术语是怎么样的?

回复
阅读 4.5k
8 个回答

@k袁_cn 分析得很清楚。

但是如果要对存在序列化传输的要求的话,这个就有问题了,因为一个已经序列化的对象,反序列化的时候会重新生成一个对象。(违法了单例)

public class Singleton implements Serializable{

    private static Singleton singleton;

    private Singleton(){
    }

    public static Singleton getInstance() {
        if (null == singleton) {
            synchronized (Singleton.class) {
                if(null == singleton)
                    singleton = new Singleton();
            }
        }
        return singleton;
    }
    private Object readResolve() {   
        return INSTANCE;
    }   
}

上面的代码变得好复杂,所以最好的单例是采用枚举实现。


public enum Singleton{
    INSTANCE;
    public Singleton getInstance(){
        return Singleton.INSTANCE;
    }
}

代码二并不安全,你这个同步块synchronized里面也是需要判断空的,会存在同时存在两个线程都进入

if(null = singleton){
    //代码块  线程1和线程2都会进入
}

最好的一种是静态内部类实现的单例(比饿汉式的更好,饿汉式只要加载Singleton类则会导致对象产生,而下面这种只有再使用了getInstance方法,也就是加载Holder类才会产生对象)

public class Singleton {

    public static Singleton getInstance(){
        return Holder.instance;
    }
    
    private static class Holder{
        private static Singleton instance = new Singleton();
    } 
}

至于你这里说的饿汉与懒汉是怎么区分的,可以明显看出来,懒汉式只有在使用对象的指定方法时才会实例化,饿汉式只要类被加载就会实例化。

public class Singleton {

    private static Singleton singleton;

    private Singleton(){
    }

    public static Singleton getsychronizedInstance() {
        if (null == singleton) {
            synchronized (Singleton.class) {
                if(null == singleton)
                    singleton = new Singleton();
            }
        }
        return singleton;
    }

}

简单的说一下单例模式中的懒汉(lazy initialization)和饿汉(eager initialization)模式。它们的区别主要就是在这个单例是在调用getInstance()时创建的、还是在类初始化时创建的。通常来说,懒汉是在调用getInstance()方法时,判断是否有已创建的实例,没有的话创建一个新的保存并返回;而饿汉则是在类的静态成员中直接创建(对应你的代码二)。

当单例模式涉及到多线程的情况时,先说结论:

  1. 尽量避免懒汉,使用饿汉。

  2. 在不得不使用懒汉时,使用内置类的静态成员方式或枚举来实现。

使用双检查的方式来实现存在这样一个问题。

if (null == singleton) { // A
    synchronized (Singleton.class) {
        if (null == singleton) {
            singleton = new Singleton(); // B
        }
    }
}
return singleton;

当两个线程A、B分别走到上述对应注释所在的行时,new Singleton()这个操作对应两个操作,分配对象内存、调用构造函数。由于A并没有在同步块中,因此可能出现singleton被分配了内存,但没有完成构造。这时,A的线程会拿到一个不完整的对象。

为了解决上述这个问题,可以通过内置类或者枚举来实现。

public class Singleton {
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    private static class SingletonHolder {
        static Singleton INSTANCE = new Singleton();
    }
    
    private Singleton() {
        // ...
    }
    
    // ...
}
public enum Singleton {
    INSTANCE;
    
    Singleton() {
        // ...
    }
    
    // ...
}

如果你想进一步了解双检查问题的缘由的话,可以参考这个博客,包含了十分详细其中涉及到的Java内存模型。

是从类加载时是否实例化对象来区分的

我一般使用内部类实现

枚举实现 简单

宣传栏