【设计模式】单例模式 之 双重检查锁单例模式为什么要用volatile关键字

并发编程的3个条件

1、原子性:要实现原子性方式较多,可用synchronized、lock加锁,AtomicInteger等,但volatile关键字是无法保证原子性的;

2、可见性:要实现可见性,也可用synchronized、lock,volatile关键字可用来保证可见性;

3、有序性:要避免指令重排序,synchronized、lock作用的代码块自然是有序执行的,volatile关键字有效的禁止了指令重排序,实现了程序执行的有序性;


new 实例背后的指令

从字节码可以看到创建一个对象实例,可以分为三步:

  1. 分配对象内存
  2. 调用构造器方法,执行初始化
  3. 将对象引用赋值给变量。

虚拟机实际运行时,以上指令可能发生重排序。以上代码 2,3 可能发生重排序,但是并不会重排序 1 的顺序。也就是说 1 这个指令都需要先执行,因为 2,3 指令需要依托 1 指令执行结果。

虽然重排序并不影响单线程内的执行结果,但是在多线程的环境就带来一些问题。

上面错误双重检查锁定的示例代码中,如果线程 1 获取到锁进入创建对象实例,这个时候发生了指令重排序。当线程1 执行到 t3 时刻,线程 2 刚好进入,由于此时对象已经不为 Null,所以线程 2 可以自由访问该对象。然后该对象还未初始化,所以线程 2 访问时将会发生异常。

volatile 作用

正确的双重检查锁定模式需要需要使用 volatile。volatile主要包含两个功能。

保证可见性。使用 volatile定义的变量,将会保证对所有线程的可见性。

禁止指令重排序优化。

由于 volatile禁止对象创建时指令之间重排序,所以其他线程不会访问到一个未初始化的对象,从而保证安全性。

注意,volatile禁止指令重排序在 JDK 5 之后才被修复

总结

对象的创建可能发生指令的重排序,使用 volatile可以禁止指令的重排序,保证多线程环境内的系统安全

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页