侧边栏壁纸
博主头像
一定会去到彩虹海的麦当

说什么呢?约定好的事就一定要做到啊!

  • 累计撰写 63 篇文章
  • 累计创建 16 个标签
  • 累计收到 3 条评论

目 录CONTENT

文章目录

volatile 关键字

一定会去到彩虹海的麦当
2022-06-01 / 0 评论 / 0 点赞 / 25 阅读 / 1,183 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-07-28,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

有序性问题

为了提高性能,在遵守 as-if-serial 语义(即不管怎么重排序,单线程下程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守。)的情况下,编译器和处理器常常会对指令做重排序。

一般重排序可以分为如下三种类型:

● 编译器优化重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
● 指令级并行重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
● 内存系统重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
从 Java 源代码到最终执行的指令序列,会分别经历下面三种重排序:

可见性问题

Java 内存模型抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中。Java 内存模型主要目的是为了屏蔽系统和硬件的差异,避免一套代码在不同的平台下产生的效果不一致。

在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。
这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。

要解决这个问题,就需要把变量声明为 volatile ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取。

所以,volatile 关键字 除了防止 JVM 的指令重排 ,还有一个重要的作用就是保证变量的可见性。

volatile 提供 happens-before 的保证,同时确保一个线程对共享变量的修改能对其他线程是可见的。当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到内存,当有其他线程需要读取时,它会去内存中读取新值。

原理

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

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

如何保证可见性

● 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
● 读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据

如何保证有序性

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

从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger。
volatile 常用于多线程环境下的单次操作 (单次读或者单次写)。

原子性

volatile不能保证原子性,比如下面这里的读取操作跟写入操作不是原子性的,所以volatile无法保证原子性

0

评论区