彻底搞清楚 java 多线程单例模式(singleton)的双重检查锁

不涉及多线程的情况下实现单例模式是非常简单的,有两种实现方式,Lazy 和 Eager 模式:

public class SingletonEager {
    //Eager模式,当Singleton被ClassLoader加载的时候创建Singleton对象
    private static SingletonEager _instance = new SingletonEager();
    private SingletonEager(){
    }

    public static SingletonEager getInstance(){
        return _instance;
    }
}

public class SingletonLazy {
    private static SingletonLazy _instance;
    private SingletonLazy(){
    }

    //lazy模式,当用户真的需要实例时才会创建
    public static SingletonLazy getInstance(){
        if (_instance == null){
             /*①*/
            _instance = new SingletonLazy();
        }
        return _instance;
    }
}

当多个线程同时调用单例时,Eager模式不会出现问题,因为每个线程只对 _instance进行读操作。但是Lazy模式会出现问题,因为各个线程不但对_instance有读操作,还有写操作,从而出现线程安全问题。

例如线程A如果执行到上述代码的①处,同时线程B开始执行if判断,发现_instance是null,这时线程A和B都会创建一个SingletonLazy对象。

按照这个逻辑,我们对上述代码进行修改,在getInstance方法上下入synchronized关键字,保证这个方法一次只能有一个线程调用。

public class SingletonLazy {
    private static SingletonLazy _instance;
    private SingletonLazy(){
    }

    //lazy模式,当用户真的需要实例时才会创建
    public synchronized static SingletonLazy getInstance(){
        if (_instance == null){
             /*①*/
            _instance = new SingletonLazy();
        }
        return _instance;
    }
}

问题得到了解决。但是,这里会出现性能问题,因为不同线程不能同时对_instance进行写,但是可以同时读,所以不应该把

if (_instance == null) 和 return _instance 放入synchronized中。 再说具体点,不同的线程可以同时判断_instance是否为null,也可以同时返回_instance。所以我们可以对上述代码进行优化:

public class SingletonLazy {
    private static SingletonLazy _instance;
    private SingletonLazy(){
    }

    //lazy模式,当用户真的需要实例时才会创建
    public static SingletonLazy getInstance(){
        if (_instance == null){
             /*①*/
            synchronized(SingletonLazy.class){
                _instance = new SingletonLazy();
            }
        }
        return _instance;
    }
}

这里之前的问题又出现了,如果线程A执行到①,与此同时线程B也执行到①(因为此时_instance 仍然为null)。所以我们可以在synchronized(this)之中加入if 判断即:d

public class SingletonLazy {
    private static SingletonLazy _instance;
    private SingletonLazy(){
    }

    //lazy模式,当用户真的需要实例时才会创建
    public static SingletonLazy getInstance(){
        if (_instance == null){
             /*①*/
            synchronized(SingletonLazy.class){
                if (_instance == null){
                    _instance = new SingletonLazy();
                }    
            }
        }
        return _instance;
    }
}

So far so good!

那么问题来了,第一个if可以省略吗?

答案是可以,但这样会降低性能,因为所有线程执行if判断 都变成了同步的,假设此时_instance不为null了,其他线程应该可以同时执行这个判断。反之,如果_instance为null,那么所有线程都应该同步执行synchronized块。

接下来还有一个问题,JVM为了优化代码执行速度,会进行指令重排,以_instance = new SingletonLazy(); 这个语句为例,它其实可以分解为三个步骤:

1. 申请一块内存空间

2. 在这个空间实例化对象

3. 将引用指向个对象

指令重排是说上述三个步骤并不是按顺序执行的,而是可能先申请一块空间,然后将引用指向这块空间,最后在创建对象。如果是但线程,这么做肯定没问题,但是在多线程情况下,如果一个线程执行到第2步,此时_instance已经不为null, 而其他线程就直接返回这块内存空间,但是这时对象还未创建,如果在调用该对象的方法,就会抛出异常。所以此时我们要禁用指令重排,只需要将_instance声明为volatile。

public class SingletonLazy {
    private volatile static SingletonLazy _instance;
    private SingletonLazy(){
    }

    //lazy模式,当用户真的需要实例时才会创建
    public static SingletonLazy getInstance(){
        if (_instance == null){
             /*①*/
            synchronized(SingletonLazy.class){
                if (_instance == null){
                    _instance = new SingletonLazy();
                }    
            }
        }
        return _instance;
    }
}

完。 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值