JUC: volatile与JMM

1 Volatile两大特性

  • 可见性
  • 有序性: 排序要求 -- 有时需要禁止指令重排

volatile内存语义:

一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。

一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,重新回到主内存中读取最新共享变量

所以volatile的写内存语义是直接刷新到主内存中,读内存语义是直接从主内存中读取。

如何保证可见性和有序性?

可见性:写后立即刷新到主内存

有序性:禁止指令重排

依靠内存屏障

2 内存屏障

内存屏障(也称内存栅栏,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此占之后的操作),避免代码重排序。内存展障其实就是一种JM指令,dava内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了java内存模型中的可见性和有序性(禁重排),但volatile无法保证原子性。

  • 内存屏障之前,所有写操作都要写回到主内存

  • 内存屏障之后,所有读操作都从主内存中读取最新结果

  • 写屏障(Store Memory Barier):告诉处理器在写屏障之前将所有存储在缓存(store bufferes)中的数据同步到主内存。也就是说当看到Store屏障指令,就必须把该指令之前所有写入指令执行完毕才能继续往下执行。

  • 读屏障(Load Memory Barier):处理器在读屏障之后的读操作,都在读屏障之后执行。也就是说在Load屏障指令之后就能够保证后面的读取数据指令一定能够读取到最新的数据。

内存屏障分类:

  • 简单分类:
    • 读屏障:在读指令之前执行读操作,让工作内存或CPU高速缓存中的数据失效,重新在主内存中获取最新数据。
    • 写屏障:在写操作之前执行的操作,强制将写缓存区的数据刷新回主内存。
  • 细分:
    • LoadLoad:Load1; LoadLoad; Load2 : 保证load1的读取操作在load2即后续操作之前执行
    • StoreStore:Store1: StoreStore: Store2: 保证Store2及之后的写操作执行前,store1的写操作已经写入主内存
    • LoadStore:Load1: LoadStore: Store2: 保证Store2进行写操作之前,Load1读操作已经执行结束
    • StoreLoad:Store1: StoreLoad: Load2: 保证Load2读操作执行前,Store1写操作的数据已经写入主内存

Java 内存模型中定义的8种每个线程自己的工作内存与主物理内存之间的原子操作:

read(读取) >> load(加载) >> use(使用) >> assign(赋值) >> store(存储) >> write(写入) >> lock(锁定) >> unlock(解锁)

  • read: 作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存
  • load: 作用于工作内存,将read从主内存传输的变量值放入工作内存变量副本中,即数据加载
  • use:作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作
  • assign: 作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到一个给变量赋值字节码指令时会执行该操作
  • store: 作用于工作内存,将赋值完毕的工作变量的值写回给主内存
  • write:作用于主内存,将store传输过来的变量值赋值给主内存中的变量

由于上述6条只能保证单条指令的原子性,针对多条指令的组合性原子保证,没有大面积加锁,所以,JVM提供了另外两个原子指令。

  • lock: 作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程。
  • unlock: 作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用

volatile 不具备原子性,比如count++

一般通过加锁实现原子性

重排序:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序

  • 不存在数据依赖关系,可以重排序;
  • 存在数据依赖关系,禁止重排序

但重排后的指令绝对不能改变原有的串行语义! 这点在并发设计中必须要重点考虑!

数据依赖性: 若两个操作访问同一变量,且这两个操作中有一个为写操作,此时两操作间就存在数据依赖性。

volatile最佳实践

  • 单一赋值,避免符合运算操作,比如i++
  • 使用布尔状态标志,判断业务是否结束
  • 读多写少场景:读不加锁,写操作加锁
  • DCL(双端锁的发布)单例模式

3 总结

  • 有可见性、有序性,没有原子性
  • 内存屏障:读屏障、写屏障
  • 内存屏障四大指令:LoadLoad, LoadStore, StoreStore, StoreLoad
  • volatile写之前的操作,禁止重排到volatile之后
  • volatile读之后的操作,禁止排序到volatile之前
  • volatile写之后volatile读,禁止重排序
posted @ 2025-09-29 08:42  飞↑  阅读(9)  评论(0)    收藏  举报