Java懒加载单例:为何用volatile而非方法级synchronized避免无效读取?
关于懒加载单例的两个问题解答
一、为什么要给字段加volatile防止无效数据读取?
在懒加载单例的双重检查锁实现里,instance = new LazySingleton()这个操作并非原子性的,JVM实际会拆成三步执行:
- 为对象分配内存空间
- 初始化对象的成员变量、执行构造方法
- 将
instance引用指向分配好的内存地址
如果没有给instance加volatile关键字,JVM可能会对这三步指令进行重排序,变成1→3→2。这时候就会出现问题:当线程A执行完步骤3但还没执行步骤2时,instance已经不为null了,此时线程B进入第一个if (instance == null)判断,会直接返回这个还没完成初始化的instance,也就是读取到了无效的半初始化对象,这就是所谓的“无效数据读取”。
volatile关键字的作用就是禁止JVM对上述指令进行重排序,确保对象完全初始化完成后,才把instance引用指向内存地址;同时它还能保证多线程之间的可见性,一个线程修改instance的值后,其他线程能立刻看到最新的结果,避免因为缓存一致性问题读取到旧值。
二、方法声明加synchronized能否替代代码块同步实现相同效果?
能实现线程安全的单例效果,但两者的性能差异很大:
- 第二种实现(方法级synchronized):整个
getInstance()方法被加锁,每次调用这个方法时,不管instance是否已经初始化,所有线程都必须排队获取锁,并发场景下性能损耗严重,相当于把懒加载变成了串行化操作。 - 第一种实现(代码块同步+双重检查):只有当
instance为null的时候,才会进入同步块抢锁;一旦instance初始化完成,后续调用getInstance()都不需要再进入同步块,直接返回实例,并发性能好很多。
另外,方法级synchronized本身也能保证内存可见性和有序性,所以不需要加volatile也不会出现无效数据读取的问题,但代价是牺牲了并发性能。
内容的提问来源于stack exchange,提问作者kaka
相关产品推荐
相关产品推荐

