双重校验锁实现对象单例(线程安全)

代码如下所示:

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

第一次判断是在Synchronized同步代码块外进行判断,由于单例模式只会创建一个实例,并通过getInstance方法返回singleton对象,所以,第一次判断,是为了在singleton对象已经创建的情况下,避免进入同步代码块,提升效率。

第二次判断是为了避免以下情况的发生。
 (1)假设:线程A已经经过第一次判断,判断singleton=null,准备进入同步代码块.
 (2)此时线程B获得时间片,由于线程A并没有创建实例,所以,判断singleton仍然=null,所以线程B创建了实例singleton。
 (3)此时,线程A再次获得时间片,由于刚刚经过第一次判断singleton=null(不会重复判断),进入同步代码块,这个时候,我们如果不加入第二次判断的话,那么线程A又会创造一个实例singleton,就不满足我们的单例模式的要求,所以第二次判断是很有必要的。

为什么还要加入Volatile呢?

首先,我们需要知道Volatile可以保证可见性和原子性,同时保证JVM对指令不会进行重排序。
 其次,这点也很关键,对象的创建不是一步完成的,是一个复合操作,需要3个指令。
 我们结合这一句代码来解释:

singleton = new Singleton();
  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

那么,这样我们就比较好理解,为什么要加入Volatile变量了。

被volatile关键字修饰的变量,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

由于volatile禁止JVM对指令进行重排序。所以创建对象的过程仍然会按照指令1-2-3的有序执行。反之,如果没有volatile关键字,假设线程A正常创建一个实例,那么指定执行的顺序可能2-1-3,当执行到指令1的时候,线程B执行getInstance方法,获取到的,可能是对象的一部分,或者是不正确的对象,程序可能就会报异常信息。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值