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

回答·97
最热
最新
  • 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 就是有个判断,cpu 是否支持高速缓存,支持就是缓存锁,不支持就是总线锁。缓存锁就有缓存一致性协议,修改当前 cpu1 里的值就会让 cpu2 里值实现,cpu 层面的一个通信,然后 cpu2 发现失效了,会去主存里面拿。以前 cpu 之间通信是阻塞方式进行的,后来 cpu 层面做了优化的。总的来说就是内存屏障当然也有 Lock 指令啊因为 java 针对不同 cpu,不同操作系统机制。
  • 要想回答这个问题,你首先得知道 volatile 解决了什么问题。 JMM 中三大特性,原子性,可见性,有序性。volatile,保证了其中的可见性和有序性,volatile 是因为 mesi 缓存一致性协议,保证了变量的可见性,而有序性是通过内存屏障来解决的,通过对 volatile 修饰的变量,前后加了内存屏障,来防止出现 jvm 对其进行指令重排而导致的线程不安全的问题。
  • volatile 的内部实现是 lock 汇编指令,其原理从 JMM 和 CPU 两个层面简单讲下。 JMM 层面实现: JMM 模型是基于 CPU 缓存和硬件模型的一个抽象设计。 在写之后插入写内存屏障,在读之前插入读内存屏障。 何为内存屏障?就是写之前将工作内存刷到主内存(写屏障),读之前从主内存读取最新数据到工作内存。 CPU 层面: CPU 运算中前会将需要的内存数据复制到高速缓存中,然后 CPU 再从高速缓存中读取运算。 如果在多核 CPU 修改共享变量,通过 MESI 协议 保证多个 CPU 缓存一致性。 举例: 1、CPU0 修改数据 比如 CPU0 修改共享变量 A 时,会把写指令放入 store buffer 中,由 store buffer 异步发送 invalid 指定给其它 CPU(关键点-异步) 2、其它 CPU 接收 invalid 指令 其它 CPU 接收到后将缓存置为失效状态,并且回执 ACK 给 CPU0。其它 CPU 发起读取共享变量 A 的时候可能会出现读的还是一个旧的值!!! 3、还是会导致缓存不一致 因为 CPU0 只有接受到其它 CPU 的 ACK 后,才会将 store buffer 中执行的写操作数据刷到缓存然后刷到主内存!在这之前,其它 CPU 读取的都是旧的值!(指令重排带来了可见性问题) CPU 的内存屏障: CPU 的 store buffer 存在目的,是为了提升 CPU 的利用率,不能让 CPU 傻傻阻塞在等其它 CPU 的 ACK。但是该优化也带来新的问题,这个问题其实也是我们上层复杂业务带来的。 这个时候咋办?操作系统说说这样吧老铁,我提供一个内存屏障的指令,你需要的时候在读写前后插入内存屏障,这种情况我们可以帮你把 store buffer 的缓存刷到内存中,这样其它 CPU 缓存就能看到最新的了!(解决了可见性) 总结: JMM 的内存屏障底层实现是根据操作系统提供的内存屏障实现缓存禁用、指令重排,实现数据的可见性。
  • 那得看是从那个层面看吧,jvm 层面是内存屏障 load 加 store,os 层面就是 lock 开头指令,字节码层面就是多了个 ass..VOLATILE,这个样子吧。
  • volatile 应该是通过 lock 前缀指令实现的内存屏障,可以通过反编译,可以清楚的看到带有 volatile 修饰的字符会有"lock addl $0✘0(%rsp)"这么一串指令,由此可见 volatile 是通过 lock 前缀指令实现的,至于怎么实现的,需要了解硬件,多核 cpu 会有 1、2、3 级缓存,当某个属性 i 被 volatile 修饰后,假设 a 线程当前持有,a 线程修改完 i 的值后,写回一级缓存时,会通过缓存一致性协议失效掉其他线程持有的 i 的值,然后 a 线程会立马写回主内存(可以理解为三级缓存),其他线程则需要重新去主内存中拿去新的值
  • cpu 级别指令,内存屏障
  • volatile 关键字在转换成指令后都会有一个 lock 前缀,任何带有 lock 前缀的指令都会有内存屏障的作用.....
  • 建议你去听下马士兵关于 java 多线程那个视频,这个涉及到 cpu 的缓存和执行顺序的问题,三言两语说不清楚。简单点来说,volattile 能保证 cpu 能按程序员设想的执行顺序来执行,而不是乱序。实际上用 java 的程序员更应该向架构着手,不是斟酌底层的优化,那些由 c++程序员来做吧