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
以上的版本。 - 还有就是从代码整洁度和阅读性来说,确实很冗长,可阅读性差。
- 另外总归是要上锁,对程序性能依然存在一定的影响。
我们能采用更好的方法,那就是静态内部类。