volatile 的原理到底是 lock 前缀指令还是内存屏障?

回答·100
最热
最新
  • volatile 的实现原理是在执行变量写操作后执行 lock 指令,这个指令会将变量实时写入内存而不是处理器的内存缓冲区,然后其他处理器通过缓存一致性协议嗅探到这个变量的变更,将该变量的缓存设为失效,从而实现可见性 在 JMM 中,通过内存屏障实现,具体如下: 会在每个 volatile 写之前插入一个 storestore 屏障,防止 volatile 写和上面的其他写重排序 在 volatile 写之后插入一个 storeload 内存屏障,防止上面 volatile 写和下面的读操作重排序 会在 volatile 读之后插入 loadload 和 loadstore 内存屏障,防止上面的 volatile 读和下面的普通读,volatile 写和普通写重排序 从而实现有序性 还有根据 happens-before 原则,每个 volatile 写 happens-before 于后续对这个变量的读
  • volatile 能保证对变量操作的可见性、有序性、原子性(仅仅只是对单个变量操作的原子性,比如说 long 类型变量)。 可见性保证:jmm 抽象了 java 对内错的操作,线程直接对共享变量的写操作只能对当前线程可见,只有将写操作 store 到主内存,然后其他线程将再将其 load 到它的本地缓存中才可见。volatile 能保证每次对共享变量的写及时 store 到主存,对共享变量的读都直接从主存中 load。 有序性保证:java 编译器将源码编译为字节码,然后 cpu 执行编译后的代码,这两个阶段都会做相应的优化,也就是会改变程序指令的执行顺序。而这个优化会保证 as-if-serial 语义,在单线程环境下不会改变程序的执行结果,但是不能保证多线程下的执行结果。而 volatile 修饰的变量在源码编译阶段保证不会发生重拍序,在 cpu 执行阶段则是在操作前后插入相应的内存屏障指令(根据不通的平台不一样,有的平台甚至本身就能保证顺序性不需要再额外插入指令)保证指令执行的顺序性。基于这一点,volatile 能保证对变量写 happens-before 读的语义。 原子性保证:java 虚拟机规范并没有规定对所有基本数据内型的操作都是原子性的,比如说 long、double 等 64 位字长的类型(有的虚拟机证默认是原子操作,比如 HotSpot),一次读写字长为 32 位。所以被 volatile 修饰的变量能在汇编指令层面上保证是原子操作,可以把它理解成一个轻量级锁,锁的范围就是被其修饰的变量。
  • 维持可见性,变量发生改变后,强制其他线程更新自己的变量副本
  • volatile 应该是通过 lock 前缀指令实现的内存屏障,可以通过反编译,可以清楚的看到带有 volatile 修饰的字符会有"lock addl $0✘0(%rsp)"这么一串指令,由此可见 volatile 是通过 lock 前缀指令实现的,至于怎么实现的,需要了解硬件,多核 cpu 会有 1、2、3 级缓存,当某个属性 i 被 volatile 修饰后,假设 a 线程当前持有,a 线程修改完 i 的值后,写回一级缓存时,会通过缓存一致性协议失效掉其他线程持有的 i 的值,然后 a 线程会立马写回主内存(可以理解为三级缓存),其他线程则需要重新去主内存中拿去新的值
  • 那得看是从那个层面看吧,jvm 层面是内存屏障 load 加 store,os 层面就是 lock 开头指令,字节码层面就是多了个 ass..VOLATILE,这个样子吧。
  • 建议你去听下马士兵关于 java 多线程那个视频,这个涉及到 cpu 的缓存和执行顺序的问题,三言两语说不清楚。简单点来说,volattile 能保证 cpu 能按程序员设想的执行顺序来执行,而不是乱序。实际上用 java 的程序员更应该向架构着手,不是斟酌底层的优化,那些由 c++程序员来做吧
  • cpu 级别指令,内存屏障
  • volatile 关键字在转换成指令后都会有一个 lock 前缀,任何带有 lock 前缀的指令都会有内存屏障的作用.....
  • 内存屏障,读写屏障,在字节码指令前加上读屏障,保证字节码指令执行时都是去主内存里读取共享变量值;在字节码指令后加上写屏障,保证每一次的写操作都能及时同步到主内存。同时还可以防止指令重排序😯
  • 百度得知,内存屏障是由 lock 指令实现的