单例模式之懒加载模式

在这里插入图片描述

一. 问题背景

公司的电商项目,创建订单的过程中,涉及加载sku信息,又涉及到加载sku库存信息。加载sku库存信息先去缓存查库存,查不到再去数据库,获取到数据再写进缓存。加载sku库存信息这里用到了锁机制,类似于单例懒加载的双重检查机制。锁是用了Redis分布式锁。 笔者对单例懒加载、Redis分布式锁都不了解,因此来研究一下。

参考自:

  1. 单例懒加载的双重检查机制
  2. 懒汉模式-双重同步锁机制
  3. 什么是单例模式

二. 什么是单例模式?

在整个应用系统中,保证一个类只有一个实例对象,实现这种功能的方式叫单例模式。

三. 为什么要用单例模式?

  1. 节省公共资源。 比如数据库连接,频繁创建、销毁数据库链接会占用很多资源。用一个数据库连接池来管理数据库连接,只要数据库连接池是单例的,那么当我用完数据库连接后就释放并交给连接池管理。等到其他人要用数据库连接则再从连接池里面拿。这样就可以避免经常创建、销毁数据库连接。除了连接池,还有日志管理、打印机、应用配置都可以用单例模式。
  2. 单例模式方便控制。 比如日志管理,多个线程同时写日志,你写一下我写一下,日志都还没写完整就被另一个线程接着写,会造成日志互窜。用单例模式控制日志的正确性,只能一个一个按照顺序来写,而单例模式只有一个人来向日志里写入方便控制,避免了这种多人干扰的问题出现。

四. 实现单例模式的思路

  1. 构造私有。 要保证一个类不能多次被实例化,就要阻止对象被new出来,所以需要把类的所有构造方法私有化。
  2. 以静态方法返回实例。 外界不能通过new获得对象,所以我们要弄一个类方法让外界获取对象实例。
  3. 确保对象实例只有一个。 只对类进行一次实例化,以后都直接获取第一次实例化的对象。

五. 懒汉模式

懒汉模式的意思是,我先不创建类的实例对象,等需要用的时候再创建。

5.1 最简单的懒汉模式

/**
 * @description: 非线程安全的懒加载单例
 * @author: ganzalang
 */
@Data
public class UnsafeLazyLoadSingleton {

    private static UnsafeLazyLoadSingleton singleton;

    private UnsafeLazyLoadSingleton() {

    }

    public static UnsafeLazyLoadSingleton getInstance() {

        if (singleton == null) {  // 1
            singleton = new UnsafeLazyLoadSingleton(); // 2
        }
        return singleton; // 3
    }
    
}

解释:如上所示,这是最简单的懒汉模式。但是它也有缺点。假如现在有2个线程分别为A、B,当A执行到3处,B执行到2,那么A线程得到return出来的是B线程new出来的。这并不是我们想要的,我们要的是A线程getInstance出来的对象,而不是B线程的。

两个线程同时进入方法造成了问题,我们想到的是给方法加锁

5.2 给方法加锁

/**
 * @description: 给方法加锁
 * @author: ganzalang
 */
@Data
public class SynchronizedMethodLazyLoadSingleton {

    private static SynchronizedMethodLazyLoadSingleton singleton;

    private SynchronizedMethodLazyLoadSingleton() {

    }

    public static synchronized SynchronizedMethodLazyLoadSingleton getInstance() {
        if (singleton == null) {
            singleton = new SynchronizedMethodLazyLoadSingleton();
        }
        return singleton;
    }
}

解释:如上所示,虽然给方法加锁可以避免前面造成的问题。但是如果频繁地访问这个对象,那么这种频繁加锁和释放锁方式就会产生严重的效率问题。

我们需要再优化一下

5.3 双重检查锁定

/**
 * @description: 双重检查锁定
 * @author: ganzalang
 */
@Data
public class DoubleCheckLazyLoadSingleton {

    private static DoubleCheckLazyLoadSingleton singleton;

    private DoubleCheckLazyLoadSingleton() {

    }

    public static DoubleCheckLazyLoadSingleton getInstance() {
        if (singleton == null) {
            synchronized (DoubleCheckLazyLoadSingleton.class) {
                if (singleton == null) {
                    singleton = new DoubleCheckLazyLoadSingleton();
                }
            }
        }
        return singleton;
    }
}

解释:如上所示,虽然加了锁和双重判断,但其实这个类是线程不安全的。现象是线程A进入同步块创建实例的时候,线程B会返回一个没有初始化的Instance对象。原因是JIT编译器会优化,会在编译时会发生“重排序”的状况。

5.4 指令重排序

编译器遇到singleton = new xxxxx();会分为如下三个步骤:

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

Java语言规范没有规定编译器的优化不会改变单线程的执行结果,但是并没有对多线程做出这样的保证。多线程情况下有时候编译器会对指令进行重排序优化,它可能把2和3颠倒过来,如下:

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

那么再分析双重检查锁定的情况:

/**
 * @description: 双重检查锁定
 * @author: ganzalang
 */
@Data
public class DoubleCheckLazyLoadSingleton {

    private static DoubleCheckLazyLoadSingleton singleton;

    private DoubleCheckLazyLoadSingleton() {

    }

    public static DoubleCheckLazyLoadSingleton getInstance() {
        if (singleton == null) { // 1
            synchronized (DoubleCheckLazyLoadSingleton.class) { // 2
                if (singleton == null) { // 3
                    singleton = new DoubleCheckLazyLoadSingleton(); // 4
                }
            }
        }
        return singleton; // 5
    }
}

当线程A执行到4时,编译器优化new xxx()的3个指令,先执行了instance = memory。然后线程B执行到1时,发现singleton不为null,那么就获得了singleton对象。然而这个singleton对象其实还没有经过ctorInstance(memory),这是不正确的。

解决双重检查锁定的2个方案:把singleton变成volatile类型禁止指令重排序

5.5 禁止指令重排序

给singleton对象加volatile

/**
 * @description: 加volatile禁止指令重排序
 */
public class VolatileDoubleCheckLazyLoadSingleton {
    private volatile static VolatileDoubleCheckLazyLoadSingleton singleton;

    private VolatileDoubleCheckLazyLoadSingleton() {

    }

    public static VolatileDoubleCheckLazyLoadSingleton getInstance() {
        if (singleton == null) { // 1
            synchronized (DoubleCheckLazyLoadSingleton.class) { // 2
                if (singleton == null) { // 3
                    singleton = new VolatileDoubleCheckLazyLoadSingleton(); // 4
                }
            }
        }
        return singleton; // 5
    }
}

用volatile修饰singleton,编译器不会对new xxx()的指令进行重排序,详情可参考《深入理解Java虚拟机》

/**
 * @description: 加volatile禁止指令重排序
 */
public class VolatileDoubleCheckLazyLoadSingleton {
    private volatile static VolatileDoubleCheckLazyLoadSingleton singleton;

    private VolatileDoubleCheckLazyLoadSingleton() {

    }

    public static VolatileDoubleCheckLazyLoadSingleton getInstance() {
        if (singleton == null) { // 1
            synchronized (DoubleCheckLazyLoadSingleton.class) { // 2
                if (singleton == null) { // 3
                    singleton = new VolatileDoubleCheckLazyLoadSingleton(); // 4
                }
            }
        }
        return singleton; // 5
    }
}

用volatile修饰singleton,编译器不会对new xxx()的指令进行重排序,详情可参考《深入理解Java虚拟机》

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值