单例模式双重检查的写法
如下为单例模式双重检查的写法, 也是属于懒汉式
package com.thread.jmm;
/**
* 类名称:Singleton1
* 类描述: 双重检查 (推荐面试使用 )
*
* @author: https://javaweixin6.blog.csdn.net/
* 创建时间:2020/9/6 19:26
* Version 1.0
*/
public class Singleton6 {
private volatile static Singleton6 INSTANCE ;
/**
* 单例模式的构造方法都是私有的, 防止其他对象new
*/
private Singleton6() {
//完成初始化等操作...
}
/**
* 返回单例模式对象
* @return
*/
public static Singleton6 getInstance() {
if (INSTANCE == null) {
synchronized (Singleton6.class) {
//再次检查INSTANCE是否已经被实例化了.
if (INSTANCE == null) {
INSTANCE = new Singleton6();
}
}
}
//如果不是空, 则直接返回实例.
return INSTANCE;
}
}
其主要的核心是两次判断INSTANCE是否为null . 主要是第二次判断的时候, 只会有一个线程进入. 如果之前的线程已经创建实例了, 那么即使是两个线程同时进入27行代码, 那么也只会创建一次实例对象.
双重检查的写法优点与常见问题
优点: 线程安全, 延迟加载(懒汉式, 只有使用到实例的时候才加载, 而不是类装载的时候加载.), 效率高
为什么要双重检查:
- 保证线程安全.
- 单个检查为什么不能保证线程安全: 如果没有31行的检查, 那么可能两个线程同时进入到27行代码的检查, 那么就会创建出两个实例对象了.
- 性能问题: 面试官可能会问如下的写法为什么不推荐. 主要是性能问题, 多个线程要获取实例的时候, 不能及时的响应.
- 为什么要加上volatile
实例对象要加上volatile的原因
- 新建立对象实际上步骤有三个, 创建对象不是原子性的.
如下的文章介绍了原子性, 只有三种情况具有原子性, 其中不包含创建对象.
https://javaweixin6.blog.csdn.net/article/details/108433038
下图说明了创建对象的过程, 例如如下图new Resource 创建对象.
正常的情况是 : 先创建一个空的resource, 接着执行第三步, 调用构造方法, call constructor. 而构造方法在图的右边那个小图, 可能是会执行一些复杂的技术, 访问数据库和磁盘操作等, 当构造方法执行完毕后, 会把创建好的实例, 赋值引用给变量rs .
这三步创建对象的过程, 对于CPU而言, 是可能会进行重排序的, 如下图则是进行了重新排序的情况, 先创建了一个空的对象, 接着就直接把引用给了rs变量, 最后才是去执行构造方法做初始化的工作. 那么其他线程在调用实例中的属性的时候, 可能会带来空指针的情况, 因为构造方法中的属性未执行. 加上volatile是为了防止重排序.
并且根据happens-before原则, 如下, 第一个线程执行31行代码, 创建好了实例, 释放了锁之后, 第二个线程获得锁之后, 肯定是看得到第一个线程已经创建好的实例的, 而且由于加上了volatile ,那么第一个线程创建好的实例, 肯定是初始化完成的, 执行了构造方法的.