单例模式的5种实现方式

饿汉模式

public class Singleton {
	//私有静态变量
	private static Singleton instance = new Singleton();
	
	//私有构造方法
	private Singleton() {
	}
	
	//公有静态函数
	public static Singleton2 getInstance (){

		return instance ;
	}
}
  • 特点
    饿汉模式在类被初始化时就已经在内存中创建了对象,以空间换时间。
    不存在线程安全问题。

懒汉模式

public class Singleton {
	//私有静态变量
	private static Singleton instance;
	
	//私有构造方法
	private Singleton() {
	}
	
	//公有静态函数
	public static Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}
}
  • 特点
    懒汉模式在方法被调用后才创建对象,实现了延时加载,以时间换空间。
    线程不安全,多线程下不能正常工作。

双重锁懒汉模式

public class Singleton {
	private volatile static Singleton instance;
	private Singleton (){
		
	}
	
	public static Singleton getInstance(){
		if(instance == null){
			synchronized (Singleton.class) {
				if(instance == null){
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}
  • 特点
    双重锁懒汉模式只有在对象需要被使用时才创建,第一次判断 instance == null是为了避免非必要加锁,当第一次加载时才对类加锁再实例化。这样既可以节约内存空间,又可以保证线程安全。
    volatile确保instance每次都从内存中读取,而主内存是被所有线程所共享的,这里的代价就是牺牲了性能,无法利用寄存器或CPU cache。

双重锁需要注意的问题
volatile 关键字可以禁止指令重排 :可以确保instance = new Singleton()对应的指令不会重排序。

因为instance = new Singleton()可以分解为以下3行伪代码:

memory = allocate();   // 1:分配对象的内存空间
ctorInstance(memory);  // 2:初始化对象 
instance = memory;     // 3:设置instance指向刚分配的内存地址

2和3之间重排序之后的执行时序如下。

memory = allocate();  // 1:分配对象的内存空间 
instance = memory;    // 3:设置instance指向刚分配的内存地址   
                       // 注意,此时对象还没有被初始化!                                                   	 
ctorInstance(memory);  // 2:初始化对象

假设多线程并发执行,2和3的重排序,将导致线程B判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象。

所以必须使用volatile来声明instance。

静态内部类模式

    /**
     * 静态内部类
     */
    private static class SingletonHolder{
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static Singleton instance = new Singleton();
    }

    /**
     * 私有化构造方法
     */
    private SingleTon() {
    }

    /**
     * 这个模式的优势在于:getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本
     */
    public static SingleTon getInstance() {
        return SingletonHolder.instance;
    }
  • 特点
    该内部类的实例与外部类的实例没有绑定关系,只有当getInstance()方法第一次被调用时,才会使虚拟机加载SingletonHolder类,创建对象。
    静态内部类模式不仅能确保线程安全,也实现了延时加载。

枚举模式

public enum Singleton {
    INSTANCE;
    
    private Singleton() {
    }
}
  • 特点
    枚举实现单例使用简洁、线程安全、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化。

Joshua Bloch大神在《Effective Java》中明确表达:

使用枚举实现单例的方法虽然还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。

我们定义的一个枚举,在第一次被真正用到的时候,会被虚拟机加载并初始化,而这个初始化过程是线程安全的。而我们知道,解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题。

所以,由于枚举的以上特性,枚举实现的单例是天生线程安全的。

猜你喜欢

转载自blog.csdn.net/sinat_34341162/article/details/84586946