volatile 原理

简介: volatile 原理

volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)

  • 对 volatile 变量的写指令后会加入写屏障
  • 对 volatile 变量的读指令前会加入读屏障

如何保证可见性

写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中

1. public void actor2(I_Result r) {
2.     num = 2;
3.     ready = true; // ready 是 volatile 赋值带写屏障
4. // 写屏障
5. }

而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据

1. public void actor1(I_Result r) {
2. // 读屏障
3. // ready 是 volatile 读取值带读屏障
4. if(ready) {
5.         r.r1 = num + num;
6.     } else {
7.         r.r1 = 1;
8.     }
9. }

如何保证有序性

写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后

1. public void actor2(I_Result r) {
2.     num = 2;
3.     ready = true; // ready 是 volatile 赋值带写屏障
4. // 写屏障
5. }

读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前

1. public void actor1(I_Result r) {
2. // 读屏障
3. // ready 是 volatile 读取值带读屏障
4. if(ready) {
5.         r.r1 = num + num;
6.     } else {
7.         r.r1 = 1;
8.     }
9. }

还是那句话,不能解决指令交错:

  • 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
  • 而有序性的保证也只是保证了本线程内相关代码不被重排序

以上的实现特点是:

  • 懒惰实例化
  • 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁
  • 有隐含的,但很关键的一点:第一个 if 使用了 INSTANCE 变量,是在同步块之外

但在多线程环境下,上面的代码是有问题的,getInstance 方法对应的字节码为:

1. 0: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;
2. 3: ifnonnull 37
3. 6: ldc #3 // class cn/itcast/n5/Singleton
4. 8: dup
5. 9: astore_0
6. 10: monitorenter
7. 11: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;
8. 14: ifnonnull 27
9. 17: new #3 // class cn/itcast/n5/Singleton
10. 20: dup
11. 21: invokespecial #4 // Method "<init>":()V
12. 24: putstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;
13. 27: aload_0
14. 28: monitorexit
15. 29: goto 37
16. 32: astore_1
17. 33: aload_0
18. 34: monitorexit
19. 35: aload_1
20. 36: athrow
21. 37: getstatic #2 // Field INSTANCE:Lcn/itcast/n5/Singleton;
22. 40: areturn

其中

  • 17 表示创建对象,将对象引用入栈 // new Singleton
  • 20 表示复制一份对象引用 // 引用地址
  • 21 表示利用一个对象引用,调用构造方法
  • 24 表示利用一个对象引用,赋值给 static INSTANCE

也许 jvm 会优化为:先执行 24,再执行 21。如果两个线程 t1,t2 按如下时间序列执行:

       关键在于 0: getstatic 这行代码在 monitor 控制之外,它就像之前举例中不守规则的人,可以越过 monitor 读取

INSTANCE 变量的值

       这时 t1 还未完全将构造方法执行完毕,如果在构造方法中要执行很多初始化操作,那么 t2 拿到的是将是一个未初

始化完毕的单例

       对 INSTANCE 使用 volatile 修饰即可,可以禁用指令重排,但要注意在 JDK 5 以上的版本的 volatile 才会真正有效

Synchronized关键字可以保证线程的互斥访问和可见性,但它并不能保证有序性。具体而言,即使一个线程已经执行完了synchronized代码块或方法,其内部对共享变量的修改操作也不一定立即对其他线程可见。

这是因为,在多个线程中,线程的调度是由操作系统和JVM共同决定的,并不是由程序控制的。因此,如果两个线程访问同一个资源,可能存在一种情况,即一个线程已经获取到了锁,但由于某种原因(如I/O操作、线程调度等),导致它暂停了一段时间,期间另一个线程也获得了锁并修改了共享变量,这样在第一个线程重新运行时,它的本地缓存并没有及时刷新,就会导致数据的不一致性。


相关文章
|
6月前
|
存储 缓存 Java
volatile底层原理详解
volatile底层原理详解
58 0
|
2月前
|
存储 缓存 Java
volatile关键字最全原理剖析
【9月更文挑战第27天】`volatile` 是一个用于修饰变量的关键字,告知编译器被修饰的变量可能在程序控制流之外被改变。在多线程环境下,`volatile` 确保变量的值每次从内存中读取,保持最新状态,但不能解决所有同步问题。在硬件交互中,`volatile` 用于内存映射的 I/O 操作,确保读取最新值。不同编程语言如 C/C++ 和 Java 中的 `volatile` 用法略有差异,但都是处理易变变量的重要工具。
|
6月前
|
缓存 Java 编译器
volatile原理
volatile原理
51 1
|
缓存 算法 安全
从内存可见性看volatile、原子操作和CAS算法
从内存可见性看volatile、原子操作和CAS算法
51 0
|
存储 缓存 安全
深入学习 volatile 的特性
深入学习 volatile 的特性
200 0
深入学习 volatile 的特性
|
存储 缓存 安全
volatile特性及实现原理
一个volatile变量自身具有以下三个特性: 1、可见性:即当一个线程修改了声明为volatile变量的值,新值对于其他要读该变量的线程来说是立即可见的。而普通变量是不能做到这一点的,普通变量的值在线程间传递需要通过主内存来完成。 2、有序性:volatile变量的所谓有序性也就是被声明为volatile的变量的临界区代码的执行是有顺序的,即禁止指令重排序。
89 0
|
SQL 缓存 安全
synchronized和volatile底层原理分析
CAS Compare And Swap (Compare And Exchange) / 自旋 / 自旋锁 / 无锁 因为经常配合循环操作,直到完成为止,所以泛指一类操作 cas(v, a, b)
91 0
volatile的特性(三)
volatile的特性: 1.保证原子性 2.没有原子性 3.指令禁止重排
114 0
volatile的特性(三)
|
存储 SQL 缓存
一个案例带你深入理解Volatile
是什么,作用是什么,我们如何用?
129 1
一个案例带你深入理解Volatile
|
缓存 安全 Java
java内存模型之volatile核心原理与应用
java内存模型之volatile核心原理与应用
132 0