快捷搜索:  汽车  科技

volatile禁止内存重排序吗(volatile可见性和防止指令重排原理)

volatile禁止内存重排序吗(volatile可见性和防止指令重排原理)volatile关键字修饰的共享变量可以提供这种可见性规范,也叫做读写可见。那么底层实现是如何通过机制保证volatile变量读写可见的?可见性:在JAVA规范中是这样定义的:java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。通俗的将就是如果有一个共享变量N,当有两个线程T1、T2同时获取了N的值,T1修改N的值,而T2读取N的值。那么可见性规范要求T2读取到的必须是T1修改后的值,而不能在T2读取旧值后T1修改为新值。一、可见性1.什么是可见性

volatile的原理


volatile禁止内存重排序吗(volatile可见性和防止指令重排原理)(1)


大家都知道,volatile可以保证可见性,一个线程对共享变量值的修改,能够及时同步到主内存,被其他线程看到。

除此之外,volatile 还可以防止指令重排序,原理是什么呢?


一、可见性

1.什么是可见性

可见性:在JAVA规范中是这样定义的:java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。通俗的将就是如果有一个共享变量N,当有两个线程T1、T2同时获取了N的值,T1修改N的值,而T2读取N的值。那么可见性规范要求T2读取到的必须是T1修改后的值,而不能在T2读取旧值后T1修改为新值。

volatile关键字修饰的共享变量可以提供这种可见性规范,也叫做读写可见。那么底层实现是如何通过机制保证volatile变量读写可见的?


2.原理

首先编译之后Java代码会被编译成字节码.class文件,在运行时会被加载到JVM中,JVM会将.class转换为具体的CPU执行指令,CPU加载这些指令逐条执行。

volatile禁止内存重排序吗(volatile可见性和防止指令重排原理)(2)

以多核CPU为例(两核),我们知道CPU的速度比内存要快得多,为了弥补这个性能差异,CPU内核都会有自己的高速缓存区,当内核运行的线程执行一段代码时,首先将这段代码的指令集进行缓存行填充到高速缓存,如果非volatil变量当CPU执行修改了此变量之后,会将修改后的值回写到高速缓存,然后再刷新到内存中。如果在刷新会内存之前,由于是共享变量,那么CORE2中的线程执行的代码也用到了这个变量,这是变量的值依然是旧的。

volatile关键字就会解决这个问题的,如何解决呢,首先被volatile关键字修饰的共享变量在转换成汇编语言时,会加上一个以lock为前缀的指令,当CPU发现这个指令时,立即做两件事:

1.将当前内核高速缓存行的数据立刻回写到内存;

2.使在其他内核里缓存了该内存地址的数据无效。


第一步很好理解,第二步如何做到呢?

MESI协议:在早期的CPU中,是通过在总线加LOCK#锁的方式实现的,但这种方式开销太大,所以Intel开发了缓存一致性协议,也就是MESI协议,该解决缓存一致性的思路是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,那么他会发出信号通知其他CPU将该变量的缓存行设置为无效状态。当其他CPU使用这个变量时,首先会去嗅探是否有对该变量更改的信号,当发现这个变量的缓存行已经无效时,会从新从内存中读取这个变量。


3.优缺点

volatile的好处:从底层实现原理我们可以发现,volatile是一种非锁机制,这种机制可以避免锁机制引起的线程上下文切换和调度问题。因此,volatile的执行成本比synchronized更低。


volatile的不足:使用volatile关键字,可以保证可见性,但是却不能保证原子操作。


◆ ◆ ◆ ◆ ◆


二、指令重排

1. 字节码层面

添加 ACC_VOLATILE


2. JVM层面

volatile内存区的读写 都加屏障

  1. LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2, 在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  2. StoreStore屏障:对于这样的语句Store1; StoreStore; Store2, 在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  3. LoadStore屏障:对于这样的语句Load1; LoadStore; Store2, 在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  4. StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2, 在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。


3. 硬件层面

硬件内存屏障 X86

sfence: store| 在sfence指令前的写操作当必须在sfence指令后的写操作前完成。

lfence:load | 在lfence指令前的读操作当必须在lfence指令后的读操作前完成。

mfence:modify/mix | 在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。

原子指令,如x86上的”lock …” 指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。

Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序。

◆ ◆ ◆ ◆ ◆

关注并后台回复 “面试” 或者 “视频”,

即可免费获取最新2019BAT

大厂面试题和大数据微服务视频

您的分享和支持是我更新的动力


·END·

后端开发技术

猜您喜欢: