设计模式 | 挑战单例模式(三)

本文探讨了Java中双重检查锁(DCL)实现单例模式的方式,旨在提高效率并确保线程安全。DCL通过两次检查实例化状态并在必要时加锁,避免了不必要的同步开销。同时,文章指出了这种方式需要使用volatile关键字以保证可见性,并提及其优缺点,包括对代码可读性和性能的影响。最后,讨论了静态内部类作为另一种优化单例模式的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


theme: channing-cyan

这是我参与8月更文挑战的第26天,活动详情查看:8月更文挑战

内容接着上篇文章《挑战单例模式(二)》,上篇文章讲到了懒汉式单例实现方式,这篇文章来讲双重检查锁实现方式。

四、双重检查锁

加锁的懒汉式的单例最大的缺陷是,每次我们想要获得单例实例时,我们都需要获得一个可能不必要的锁。

为了解决这个问题,我们可以首先从验证是否需要创建对象开始,并且只有在这种情况下我们才会去获得锁。

public class SingletonInDoubleCheckLock {   private volatile static SingletonInDoubleCheckLock INSTANCE = null; ​   private SingletonInDoubleCheckLock(){} ​   public static SingletonInDoubleCheckLock getInstance(){       if(INSTANCE == null){           synchronized (SingletonInDoubleCheckLock.class){               if(INSTANCE == null){                   INSTANCE = new SingletonInDoubleCheckLock();               }           }       }       return INSTANCE;   } }

同样的,我们也有两个线程去并发的调用getInstance方法。

  • 任意一个线程进入方法后,先检查变量是否被初始化(不去获得锁),如果已被实例化则立即返回。
  • 如果对象为实例化,那就再检查能否获取锁。
  • 再次检查变量是否已经被实例化,如果还没被初始化就初始化一个对象。

使用双重检查,非常巧妙地兼顾效率和保证线程安全。

  • 第一道检查,可以保证对象如果已经实例化,对于进入到第一道检查的线程,直接获取实例化的对象返回。不再抢占获取锁资源。
  • 第二道检查,可以保证对象如果已经被实例化, 刚刚获取到锁资源的线程,直接获取实例化的对象返回,不再实例化新的对象。

但能保证两道检查能够顺利按照预期执行的前提是,使用了volatile关键字修饰INSTANCE变量,重排序被禁止,所有的写(write)操作都将发生在读(read)操作之前,保证了实例化的对象对所有用到INSTANCE的地方可见。

volatile关键字感兴趣的读者可以跳转到《面时莫慌 | 你好,请谈谈volatile关键字?(全篇)》全面了解。

双重检查锁还有一个优点是,它控制了加锁的粒度,懒汉式的加锁方式是对整个静态方法加锁,如果发生阻塞就是基于整个类的阻塞,而双重检查锁是在方法内部的阻塞,可以在逻辑层面避免锁的抢夺。

双重检查锁也有明显的缺陷

  • 要用到volatile关键字保证可见性,这个关键字要用在Java1.4以上的版本。
  • 还有就是从代码整洁度和阅读性来说,确实很冗长,可阅读性差。
  • 另外总归是要上锁,对程序性能依然存在一定的影响。

我们能采用更好的方法,那就是静态内部类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值