Java内存模型(JMM)
描述
通过JMM可以屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果,即定义了程序中共享内存变量的访问规则、底层细节。同时,Java线程之间的通信也是由JMM来控制完成的。JMM规定所有的共享变量(实例字段、静态字段、数组)都是存储在主内存当中的,但是每个线程还有自己的工作内存,工作内存中保存了该线程使用到的变量的主内存的副本。线程对变量的所有操作都是在工作内存中进行的,而不能对主内存中的数据进行操作。同时,不同线程线程之间也无法直接访问对方工作内存中的变量,线程之间的变量值传递必须通过需要通过主内存来完成。
JMM定义了8个原子的、不可再分的操作来完成工作内存和主内存之间的数据交换:
以及相应的规则:
happens-before
我们可以使用happens-before原则来判断在并发环境中两个操作之间是否安全,是否具有原子性、可见性、顺序性方面的问题(只要不改变程序运行的结果,JMM允许编译器和处理器进行任何优化),进而使用相应的手段来解决,而不用通过JMM的规则去判断。
如果A happens-before B,则A操作的执行结果将对B操作可见,但是并不意味着必须按照A先于B的顺序来执行,如果重排序后执行结果与按照happens-before关系来执行的结果一致,则JMM允许这种重排序。
as-if-serial
不管(编译器和处理器)怎么重排序,单线程程序的执行结果都不能改变。为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。
- as-if-serial语义保证了单线程内程序的执行结果不会被改变,happens-before关系保证了同步正确的多线程程序的执行结果不会被改变。
- as-if-serial语义给编写单线程程序的人创造了一个幻境:单线程程序是按程序的顺序来执行的,happens-before关系给编写正确同步的多线程程序的人创造了一个幻境:正确同步的多线程程序是按照happens-before指定顺序来执行的。但是事实上,编译器和处理器为了提高并行度会进行各种优化和重排序。
volatile
- 当写一个volatile变量时,JMM会把该线程对应的工作内存中的共享变量刷新到主内存。
- 当读一个volatile变量时,JMM会把该线程对应的工作内存置为无效,然后去主内存中读取共享变量。
锁
- 当线程释放锁时,JMM会把该线程对应的工作内存中的变量刷新到主内存当中。
- 当线程获取锁时,JMM会把该线程对应的工作内存置为无效,从而使得必须从主内存中读取共享变量。
注:volatile写和锁的释放、volatile读和锁的获取有相同的内存语义和内存效果。
final
- 编译器会在final域写之后,构造函数return前,插入一个StoreStore屏障,禁止处理器把final域的写重排序到构造函数外。
- 编译器会在读final域操作之前加一个LoadLoad屏障,禁止处理器将初次读对象引用与初次读该对象包含的final域重排序。
如果你想了解更多我对编程和人生的思考,请关注公众号:青云学斋