Java多线程 单例模式双重检查的写法 (面试常考点)

单例模式双重检查的写法

如下为单例模式双重检查的写法, 也是属于懒汉式

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行代码, 那么也只会创建一次实例对象.

双重检查的写法优点与常见问题

优点: 线程安全, 延迟加载(懒汉式, 只有使用到实例的时候才加载, 而不是类装载的时候加载.), 效率高

为什么要双重检查:

  1. 保证线程安全.
  2. 单个检查为什么不能保证线程安全: 如果没有31行的检查, 那么可能两个线程同时进入到27行代码的检查, 那么就会创建出两个实例对象了.
  3. 性能问题: 面试官可能会问如下的写法为什么不推荐. 主要是性能问题, 多个线程要获取实例的时候, 不能及时的响应.
  4. 为什么要加上volatile
    实例对象要加上volatile的原因
  5. 新建立对象实际上步骤有三个, 创建对象不是原子性的.
    如下的文章介绍了原子性, 只有三种情况具有原子性, 其中不包含创建对象.
    https://javaweixin6.blog.csdn.net/article/details/108433038
    下图说明了创建对象的过程, 例如如下图new Resource 创建对象.
    正常的情况是 : 先创建一个空的resource, 接着执行第三步, 调用构造方法, call constructor. 而构造方法在图的右边那个小图, 可能是会执行一些复杂的技术, 访问数据库和磁盘操作等, 当构造方法执行完毕后, 会把创建好的实例, 赋值引用给变量rs .
    这三步创建对象的过程, 对于CPU而言, 是可能会进行重排序的, 如下图则是进行了重新排序的情况, 先创建了一个空的对象, 接着就直接把引用给了rs变量, 最后才是去执行构造方法做初始化的工作. 那么其他线程在调用实例中的属性的时候, 可能会带来空指针的情况, 因为构造方法中的属性未执行. 加上volatile是为了防止重排序.

并且根据happens-before原则, 如下, 第一个线程执行31行代码, 创建好了实例, 释放了锁之后, 第二个线程获得锁之后, 肯定是看得到第一个线程已经创建好的实例的, 而且由于加上了volatile ,那么第一个线程创建好的实例, 肯定是初始化完成的, 执行了构造方法的.

猜你喜欢

转载自blog.csdn.net/qq_33229669/article/details/108435929