双重锁检查单例、枚举单例、静态内部类单例

单例最基本要素:

  1. 私有静态属性,用于存取类的唯一实例。
  2. 公共静态方法,用于提供对该唯一实例的存取访问,如果实例未创建,则创建该实例。
  3. 用于限制类再次实例化的方式。通常使用私有构建函数的方式来实现。

最简单的单例

public class Singleton {
     
    private Singleton() {
     }  //私有构造函数
    private static Singleton instance = null;  //单例对象
    //静态工厂方法
    public static Singleton getInstance() {
     
        if (instance == null) {
     
            instance = new Singleton();
        }
        return instance;
    }
}

上述单例线程不安全,如在instance未实例化时,就有多个线程一起调用getInstance()方法,则会创建出多个对象。

双重加锁单例实现

public class Singleton {
     
    private Singleton() {
     }  //私有构造函数
   private static Singleton instance = null;  //单例对象
   //静态工厂方法
   public static Singleton getInstance() {
     
        if (instance == null) {
           //双重检测机制
         synchronized (Singleton.class){
       //同步锁
           if (instance == null) {
          //双重检测机制
             instance = new Singleton();
               }
            }
         }
        return instance;
    }
}

经过两个加锁检测,看上去是很安全了,但是还是有线程安全问题,因为涉及到了JVM编译器的指令重排。

指令重排是什么意思呢?

比如java中简单的一句 instance = new Singleton,会被编译器编译成如下JVM指令:

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

但是这些指令顺序并非一成不变,有可能会经过JVM和CPU的优化,指令重排成下面的顺序:

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

我们需要在instance对象前面增加一个修饰符volatile。

public class Singleton {
     
    private Singleton() {
     }  //私有构造函数
    private volatile static Singleton instance = null;  //单例对象
    //静态工厂方法
    public static Singleton getInstance() {
     
          if (instance == null) {
           //双重检测机制
         synchronized (Singleton.class){
       //同步锁
           if (instance == null) {
          //双重检测机制
             instance = new Singleton();
                }
             }
          }
          return instance;
      }
}

volatile关键字:

  1. 阻止了变量访问前后的指令重排,保证了指令执行顺序。
  2. 保证线程访问的变量值是主内存中的最新值。

用静态内部类实现单例模式:

public class Singleton {
     
    private static class LazyHolder {
     
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){
     }
    public static Singleton getInstance() {
     
        return LazyHolder.INSTANCE;
    }
}
  1. 从外部无法访问静态内部类LazyHolder,只有当调用Singleton.getInstance方法的时候,才能得到单例对象INSTANCE。
  2. INSTANCE对象初始化的时机并不是在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类LazyHolder被加载的时候。因此这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。

双重锁检查单例、静态内部类单例都可以实现线程安全的单例模式,但是不能防止用反射的方式打破单例,如下。

//获得构造器
Constructor con = Singleton.class.getDeclaredConstructor();
//设置为可访问
con.setAccessible(true);
//构造两个不同的对象
Singleton singleton1 = (Singleton)con.newInstance();
Singleton singleton2 = (Singleton)con.newInstance();
//验证是否是不同对象
System.out.println(singleton1.equals(singleton2));

如何创建出线程安全又不能被反射破坏单例性的单例呢?

答案是用枚举实现单例模式。

枚举实现单例模式

/**
 * 最完美的单例模式
 * @author Administrator
 *
 */
public class MySingleton {
     

	public enum MyEnumSingle{
     
		INSTANCE;
		private MySingleton singleOne;
		
		private MyEnumSingle(){
     
			System.out.println("初始化单例");
			singleOne = new MySingleton();
		}
		
		public MySingleton getInstance(){
     
			return singleOne;
		}
	}
	
	private MySingleton(){
     }
	
	public static MySingleton getInstance(){
     
		return MyEnumSingle.INSTANCE.getInstance();
	}
	
}
  1. 枚举模式的单例是饿汉模式
  2. 使用枚举实现的单例模式,不但可以防止利用反射强行构建单例对象,而且可以在枚举类对象被反序列化的时候,保证反序列的返回结果是同一对象。

双重锁检查单例、枚举单例、静态内部类单例_第1张图片

你可能感兴趣的:(设计模式,单例模式)