详解单例模式

162 阅读3分钟

单例模式

当我们的程序中只需要某个类存在一个对象时,比如web应用中的ServletContext 的对象、Spring中的AppliationContext 的对象,都只需要一个,从程序运行开始,到程序结束。

所以我们需要使用单例模式来创建这些对象。

单例模式之饿汉式

饿汉式:顾名思义,饿汉式会迫不及待地创建对象,所以在类加载完成后就会创建对象。

class Singleton{
	// 单例模式不允许使用new 方式创建对象,所以需要将构造器私有化
    private Singleton(){
    }
    
    private static final Singleton SINGLETON=new Singleton();

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

​ 还可以使用静态块进行初始化,使用静态块初始化是在类初始化阶段给SINGLETON赋值。使用上面这种常量方式会在类的链接中的准备阶段显示初始化,而类加载是一个线程安全的操作,不会创建两个对象。

​ 但是饿汉式的单例模式有一个很大的缺点,如果这个类存在占用很大内存的成员变量,对象创建后就会占用极大的内存,但是仅仅是加载了这个类,而不需要这个类的对象,就会造成空间的浪费。

​ 所以我们需要懒汉式。

单例模式之懒汉式

懒汉式:顾名思义,很懒,所以是在我们需要的时候才会去创建对象,就不会造成空间浪费的情况。

/**
 *	 这种方式线程不安全
 */
class Singleton {
    private Singleton() {
    }
    
    private static Singleton singleton;
    
    public static Singleton getInstance() {
        if (singleton == null) {
            // 如果一条线程运行到了这里,cpu切换了线程,会造成多条线程获取的对象不一样
            singleton = new Singleton();
        }
        return singleton;
    }
}
/**
 *	 加了同步锁的方法,效率低
 */
class Singleton {
    private Singleton() {
    }
    
    private static Singleton singleton;
    
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

/**
 *	 使用同步代码块方式,双重检查,效率可以
 */
class Singleton{

    private Singleton(){
    }
    
    private static volatile Singleton singleton;

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

​ 在上面我们使用了==volatile==关键字修饰单例对象。

创建对象的步骤:

  1. 加载类元信息
  2. 为对象分配内存
  3. 处理并发问题
  4. 属性的默认初始化(零值初始化)
  5. 设置对象头信息
  6. 属性的显示初始化、代码块中初始化、构造器中初始化
  7. 将引用指向这个对象

但是由于指令重排的问题,可能对象还未初始化的时候,引用就指向了对象,所以需要使用volatile来防止指令重排。

因为类加载是线程安全的,所以可以使用内部类的方式创建单例对象。

class Singleton{

    private Singleton(){
    }

    private static class InnerClass{
        private static final Singleton SINGLETON=new Singleton();
    }

    public static Singleton getInstance(){
        return InnerClass.SINGLETON;
    }

}

​ 但是无论使用饿汉式还是懒汉式都无法阻挡==反射==创建对象。反射可以无视构造器的访问权限,所以最安全的单例模式只有枚举类了。

单例模式之枚举类

enum Singleton{
    SINGLETON
}

​ 枚举的值就是枚举类的一个对象,为什么枚举类可以防止反射呢?

​ java.lang.reflect.Constructor 类有

if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");

会抛出异常。

​ 使用Class对象的 newInstance() 方法创建枚举类对象会抛出 java.lang.InstantiationException。

并且它在 jdk9的时候被标记为 @Deprecated 。