背景
本文将讲解单例模式-双重检验锁的实现。
代码实现
public class DCLTest {
private volatile static DCL instance;
private DCLTest () {};
public static DCLTest getInstance() { // 不用synchronized修饰方法,效率高
if (instance == null) {
synchronized (DCLTest .class) {
if (instance == null) {
instance = new DCLTest ();
}
}
}
return instance;
}
}
实现思路
这边主要解释以下几个问题。
1.为什么要进行两次判空?
答:首先,存在即合理。观察代码,synchronzied内部这个判空是必须要进行的,因为我们是单例嘛,ioc容器中只存在一个单例对象就好了。多线程的时候,如果这个对象已经被创建,那其他线程就不用继续去new一个实例对象,如果不存在,才去new这个单例对象。
对于synchronzied以外的这个判空,其实是做了一个优化。因为在多线程环境下,如果不加这个判空的话,每个线程访问这个方法的时候,都会直接访问到synchronized块,从而对类对象进行加锁。我们知道,加锁这个操作对性能消耗还是挺大的,因为会引起线程的上下文切换,内核态和用户态的转换。尤其是并发环境下,这个性能消耗会更加明显。
所以,外层的判空是针对性能做出的优化,避免每个线程都去加锁。只有实例对象为空的时候,当前线程才会去加锁。
2.为什么使用volatile修饰instance实例对象?
答: 这边使用volatile修饰,主要是为了禁止指令重排序,从而避免由于指令重排序而带来的一个问题(可能会访问一个还未进行初始化的对象)。
因为如果访问一个还未初始化的对象,会报空指针异常。
3.为什么使用synchronzied来修饰代码块?synchronzied为什么没有直接修饰这个getInstance()方法?
答:synchronized块用来保证在多线程环境下只有一个线程可以执行实例化操作。
不直接修饰方法其实也是为了提高程序的效率。因为如果直接修饰方法的话,对于每个访问的线程,都要先去获取锁资源,再执行相应的逻辑。
直接修饰代码块,就可以避免每个线程都竞争锁资源了。