DCL(Double Check Lock) + volatile 实现单例

DCL(Double Check Lock) + volatile 实现单例

常见单例实现的方式分为饿汉式与懒汉式。

  • 饿汉式
    从字面意思不难理解,如同饿汉一样,一上来就抱着馒头啃。
  • 懒汉式
    而懒汉式就比较优雅了,饿了才去找馒头吃。
  1. 饿汉式实现的方式:
    1. 静态常量
    2. 静态代码
    3. 块枚举
  2. 懒汉式实现方式:
    1. 静态工厂方法(非线程安全)
    2. 静态工厂方法 + synchronized(线程安全,效率不高)
    3. 静态工厂方法 + synchronized + if判断(效率解决了,但是线程不安全)
    4. DCL双重效应(看似效率不错,线程也安全。但是它会导致对象未初始化)
    5. DCL + volatile(线程安全,效率不错。说synchronized效率低的,请查询下JDK1.5后对synchronized锁优化、锁升级过程)

本文将主要讲解DCL + volatile 实现单例原理,其他方式本文将不再做过多陈述。希望能用简单的白话,让大家能清晰易懂。

在编写代码之前,我们先简单了解下 volatile 关键字特性。
1.保证多线程下变量的可见性
2.禁止指令重排
3.不保证原子性
这里就不详细讲解volatile原理了,这不是本文重点,我们只需指定它的作用便可,若感兴趣的下方留言,后期我可专门发布一篇刨析volatile原理文章。

好啦,废话说了一大堆,直接上干货。

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (null == singleton) { // 1-1,2-1
            synchronized (Singleton.class) { // 1-2,2-2(wait)
                if (singleton == null) { // 1-3,2-3
                    singleton = new Singleton(); // 1-4
                }
            }
        }
        return singleton;
    }
}

这里我们假设1-1 表示 线程1 步骤1,2-1表示 线程2 步骤1。

步骤 1:当线程1和2同时进入,首先进行if判断,发现是null那么继续向下执行。(这里的判断不难理解,如果有就返回,没有就创建)
步骤 2:此时线程1和2进行抢占锁,这里假设线程1抢到锁,线程2需等待。
步骤 3:线程1先进入步骤3,判断是否为null,为null便进行创建对象;然后释放锁,返回对象。(为什么需要再次判断?如果不进行判断,线程1执行完后,线程2进来也会再次创建对象)

这么分析好像发现DCL看似没有任何毛病,但是可别忘记为什么加volatile。我们来简单的再次分析下,new 一个对象的过程

  1. 在堆区分配对象所需要的内存(JVM内存分:堆区、方法区、栈。他们各自区别,可参考此篇文章)
  2. 变量赋默认值
  3. 执行构造方法,初始化
  4. 返回引用地址

我们知道CPU执行是无序的,往往代码若重排后将会打乱执行的顺序,使程序出现不符合预期的结果。如果创建对象指令被重新排列可能会出现如:1–>2–>4–>3的执行情况。那么我们上面代码的执行可能会出现如下结果:

线程1,在执行第4步时,发生指令重排(1–>2–>4–>3),此时线程1返回的引用地址,而线程2执行步骤3进行判断发现不为空,直接进行返回了,而返回的对象并没有进行初始化。接着报错对象尚未初始化。

至此各位知道DCL模式为什么必须要加volatile关键字了吧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值