为什么双重检验锁需要两次判空?

背景

本文将讲解单例模式-双重检验锁的实现。

代码实现

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块用来保证在多线程环境下只有一个线程可以执行实例化操作。
不直接修饰方法其实也是为了提高程序的效率。因为如果直接修饰方法的话,对于每个访问的线程,都要先去获取锁资源,再执行相应的逻辑。
直接修饰代码块,就可以避免每个线程都竞争锁资源了。

posted @ 2024-08-12 22:00  heyhy  Views(102)  Comments(0Edit  收藏  举报
Title