volatile、synchronized、final内存语义

mac2026-06-05  5

文章目录

volatile内存语义synchronized内存语义final内存语义

volatile内存语义

当写volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。当读volatile变量时,volatile变量的每次使用都必须从主内存刷新最新的值。

JMM针对编译器对编译器指定的volatile重排序规则表

是否能重排序第二个操作第一个操作普通读/写volatile读volatile写普通读/写NOvolatile读NONONOvolatile写NONO

当第二个操作为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域的对象的引用。

最新回复(0)