单例模式的两种创建形式
积极加载,在类加载的时候直接创建初始化对象,代码如下:
/**
* 如果是饿汗模式 直接初始化 每个对象获取都是一个对象
* <p>
* 懒加载
*/
public static SingleInstance instance = new SingleInstance();
//第一种
public static SingleInstance getInstance1() {
if (instance == null) {
instance = new SingleInstance();
}
return instance;
}
对于大量没有使用的对象,因为频繁创建对象会消耗额外的内容,所以一般会根据具体场景使用另外一种加载模式,当然上面这种模式的优点在于书写简单,且线程安全
懒汉式加载的几种实现
第一种,简单明了
public static SingleInstance instance2;
/**
* 多线程模式下 如果线程1走到了3 线程2走到了1 或者2 都会生成新的instance
* 这个 需要加锁解决 避免多次创建对象 消耗资源 且造成线程之间共享数据不安全
*
* @return
*/
public static SingleInstance getInstance2() {
if (instance == null) { //1
instance = new SingleInstance(); //2
}
return instance; //3
}
针对第一种现象,为了保证线程之间的安全性,我们采用加锁的方式
public static synchronized SingleInstance getInstance3() {
if (instance == null) {
instance = new SingleInstance();
}
return instance;
}
加锁 虽然给方法加锁可以避免前面造成的问题。但是如果频繁地访问这个对象, 那么这种频繁加锁和释放锁方式就会产生严重的效率问题。为了保证并发效率,采用双重检测
public static synchronized SingleInstance getInstance4() {
if (instance == null) { //1
synchronized (SingleInstance.class){//2
//加类锁
if(instance ==null){ //3
instance = new SingleInstance(); //4
}
}
}
return instance; //5
}
双重检测
虽然加了锁和双重判断,但其实这个类是线程不安全的。 现象是线程A进入同步块创建实例的时候,线程B会返回一个没有初始化的Instance对象。 原因是JIT编译器会优化,会在编译时会发生“重排序”的状况。 线程A 走到了 5 线程B 走到了2 这个时候因为线程A已经释放了类锁 所以线程B就会进入到 3 再次判断 instance是否为空, 这就是JVM 模型的问题 , 因为每一个线程的工作内存都拷贝一份
指令重排序
编译器遇到singleton = new xxxxx();会分为如下三个步骤:
memory = allocate() // 1. 分配对象的内存空间
ctorInstance(memory) // 2. 初始化对象
instance = memory; // 3. 设置instance指向刚分配的内存地址
Java语言规范没有规定编译器的优化不会改变单线程的执行结果,但是并没有对多线程做出这样的保证。多线程情况下有时候编译器会对指令进行重排序优化,它可能把2和3颠倒过来,如下:
memory = allocate() // 1. 分配对象的内存空间
instance = memory; // 3. 设置instance指向刚分配的内存地址
ctorInstance(memory) // 2. 初始化对象
结合指令重排序分析上面的双重检测机制,就会发现 当线程A走到5的时候,先执行了instance = memory; 然后线程B执行到1时,发现singleton不为null,那么就获得了singleton对象。然而这个singleton对象其实还没有经过ctorInstance(memory),这是不正确的。
所以为了保证双重检测的安全性,禁止指令重排序,在实际开发中会采用 volatile关键字,volatile的具体使用,以及volatile是如何利于JVM内容屏障机制以及JMM来保证可见性以及禁止指令重排
public static volatile SingleInstance instance = new SingleInstance();
public static synchronized SingleInstance getInstance4() {
if (instance == null) { //1
synchronized (SingleInstance.class){//2
//加类锁
if(instance ==null){ //3
instance = new SingleInstance(); //4
}
}
}
return instance; //5
}