volatile内存语义
- 当写volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
- 当读volatile变量时,volatile变量的每次使用都必须从主内存刷新最新的值。
JMM针对编译器对编译器指定的volatile重排序规则表
是否能重排序 | 第二个操作 | ||
第一个操作 | 普通读/写 | volatile读 | volatile写 |
普通读/写 | NO | ||
volatile读 | NO | NO | NO |
volatile写 | NO | NO |
当第二个操作为volatile写,不管第一个操作是什么都不能进行重排序。确保volatile写之前的操作不会被编译器重排序到volatile写之后。
当第一个操作位volatile读,不管第二个操作是什么都不能进行重排序。确保volatile读之后的操作不会被编译器重排序到volatile读之前。
当第一个操作是volatile写,第二个操作是volatile读或者volatile写时不能重排序。语义
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列插入内存屏障来禁止特定类型的处理器重排序。下面是基于JMM内存屏障插入策略:
- 在每个volatile写操作的前面插入一个StoreStore屏障
- 在每个volatile写操作的后面插入一个StoreLoad屏障
- 在每个volatile读操作的后面插入一个LoadLoad屏障
- 在每个volatile读操作的后面插入一个LoadStore屏障
volatile写插入内存屏障指令序列示意图
普通读 |
---|
普通写 |
StoreStore屏障(禁止上面的普通写与下面的volatile写重排序) |
volatile写 |
StoreLoad屏障(禁止上面的volatile写与下面可能有的volatile读/写重排序) |
volatile读插入内存屏障指令序列示意图
volatile读 |
---|
LoadLoad屏障(禁止下面的普通读和上面的volatile读重排序) |
LoadStore屏障(禁止下面的普通写和上面的volatile读重排序) |
普通读 |
普通写 |
synchronized内存语义
锁可以让临界区互斥执行。
当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。
当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。
线程A释放锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
线程B获取锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
线程A释放锁,随后线程B获取锁,这个过程实质上是线程A通过主内存向线程B发送消息。
释放锁与volatile写有相同的内存语义,获取锁与volatile读有相同的内存语义。
final内存语义
public class FinalTest {
int i;
final int j;
static FinalTest obj;
public FinalTest() {
i = 1;
j = 2;
}
public static void write() {
obj = new FinalTest();
}
public static void read() {
FinalTest object = obj;
int a = object.i;
int b = object.j;
}
}
写final域的重排序规则禁止把final域的写重排序到构造函数之外。这个规则可以保证,在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保证。
读final域的重排序规则是:在一个线程中,初次读对象引用与初次读对象包含的final域,JMM禁止重排序这两个操作。这个规则可以保证,在读一个对象的final域之前,一定会先读包含这个final域的对象的引用。