Java中的volatile

volatile的内存语义

内存可见性

volatileJava虚拟机提供的轻量级的同步机制,它有3个特性:
1)保证可见性
2)不保证原子性
3)禁止指令重排

volatileJava提供的一种轻量级的同步机制,在并发编程中,它也扮演着比较重要的角色。同synchronized相比(synchronized通常称为重量级锁),volatile更轻量级,相比使用synchronized所带来的庞大开销,倘若能恰当的合理的使用volatile,自然是美事一桩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class volatileDemo1 {
static boolean flag = true;

public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t -----come in");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
},"t1").start();

new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t -----come in");
while (flag) {

}
System.out.println(Thread.currentThread().getName()+"\t -----flag被设置为false,程序停止");
},"t2").start();
}
}

上面这个例子,模拟在多线程环境里,t1线程对flag共享变量修改的值能否被t2可见,即是否输出 “—–flag被设置为false,程序停止” 这句话?

这个结论会让人有些疑惑,可以理解。因为倘若在单线程模型里,因为先行发生原则之happens-before,自然是可以正确保证输出的;但是在多线程模型中,是没法做这种保证的。因为对于共享变量flag来说,线程t1的修改,对于线程t2来讲,是”不可见”的。也就是说,线程t2此时可能无法观测到flage已被修改为false。那么什么是可见性呢?

所谓可见性,是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更,JMM规定了所有的变量都存储在主内存中。很显然,上述的例子中是没有办法做到内存可见性的。

volatile的内存语义

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

所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取,从而保证了可见性。

volatile变量有2大特点,分别是:

  • 可见性

  • 有序性:禁重排!

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

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

​ 但重排后的指令绝对不能改变原有串行语义!

那么volatile凭什么可以保证可见性和有序性呢??

  • 内存屏障Memory Barrier~

内存屏障在另外一篇文章里面.

volatile有关禁重排的行为

  1. 当第一个操作为volatile读时,不论第二个操作是什么,都不能重排序。这个操作保证了volatile读之后的操作不会被重排到volatile读之前(volatile读之后的操作,都禁止重排序到volatile之前)
  2. 当第二个操作为volatile写时,不论第一个操作是什么,都不能重排序。这个操作保证了volatile写之前的操作不会被重排到volatile写之后 (volatile写之前的操作,都禁止重排序到volatile之后)
  3. 当第一个操作为volatile写时,第二个操作为volatile读时,不能重排。(volatile写之后volatile读,禁止重排序的)

内存屏障四大指令插入情况

  1. 在每个volatile操作的前面插入一个StoreStore屏障,保证在volatile之前,其前面的所有普通操作都已经刷新到主内存中。
  2. 在每个volatile操作的后面插入一个StoreLoad屏障,避免volatile与后面可能有的volatile读/写操作重排序
  3. 在每个volatile操作的后面插入一个LoadLoad屏障,禁止处理器把上面的volatile与下面的普通重排序。
  4. 在每个volatile操作的后面插入一个LoadStore屏障,禁止处理器把上面的volatile与下面的普通重排序。

Java中的volatile
https://zhaops-hub.github.io/2024/06/09/java/Java中的volatile/
作者
赵培胜
发布于
2024年6月9日
许可协议