Java(多线程)[volatile]

内存模型

内存模型
主内存中的数据是共享的,由于操作主内存太慢了,每个线程都有一份副本,所有读写都在线程独有的工作内存中完成,再同步到主内存中。

栗子

现在有一个静态变量 x:

static int x = 0;

线程A执行:

x = 2;
  • 第一步:从主内存同步 x 的值。
    同步主内存中的数据
  • 第二步:在工作内存中修改 x 的值为 2。
    修改工作内存中的变量值
  • 第三步:把工作内存中的数据同步到主内存中
    把数据同步到主内存
    从上述步骤来看,同步数据需要一个过程,如果线程B想要获取 x 的值,即使线程A先执行了,也会有两种结果:
    一是线程A执行完毕,x 的值也已经同步到主内存,可以取出 x = 2;
    二是线程线程A虽然执行完了,但 x 的值还没有同步到主内存中,这时线程B只能取到 x = 0。
    如果希望各线程共享一个变量,且保证每个线程都能得到最新的变量值,我们可以使用 synchronized 同步锁,但效率太低,严重影响了性能,这就用到了 volatile。

Volatile

可见性

使用 volatile 修饰的变量对所有线程具有可见性,这就解决了我们上边遇到的问题:当一个线程改变了变量的值,会立刻同步到主内存中,其它线程读取时,也会从主内存中得到最新的值。

原理

volatile 的可见性是基于先行发生原则的,也就是说,对 volatile 变量的写操作,先行发生于后面对这个变量的读操作。

适用场景

  • 只有一个线程对变量进行修改,或不依赖于变量的当前值。

假设现在有线程A和线程B,同时对变量 x 做多次修改,虽然它们取到的值都是最新的,但修改操作并不是原子性的,可能从取值到修改完毕之间,变量值已经被修改了很多次,这时就相当于在旧的变量值上做修改,无法保证线程安全。

  • 变量不需要作为状态约束。
volatile int num = 10;
volatile int max = 20;

线程A:

while(num < max) {
	// 执行操作
}

线程B:

num += 10;
max += 10;

如果线程A在执行时,线程B先修改了 num 的值为 20,还没来得及修改 max,线程A的判断就去取值,则会导致线程A终止。

指令重排

目的

在不影响程序执行结果的前提下(这里指的是单线程中的运行结果),优化程序的运行效率。

实现

在 JVM 编译代码,或 CPU 执行 JVM 字节码时,对指令进行重新排序。

栗子

// 初始化状态
boolean finishInit = false;

线程A:

// 初始化
init();
// 将初始化状态修改为 true
finishInit = true;

线程B:

while(!finishInit) {
	sleep(100);
}
// 执行操作
exec();

这段代码的意思是:线程A做初始化工作,完成后把标识修改为 true,线程B等到初始化状态为 true 时,开始执行操作。
如果线程A被指令重排,很可能初始化状态先变成了 true,才去执行 init() 方法,这就导致了线程B直接执行操作,由于没有初始化完成而发生错误。
想要防止有序的代码被指令重排,我们也可以使用 volatile 来修饰变量,它为我们插入了内存屏障。

内存屏障

内存屏障隔开了操作,防止屏障前后的操作被进行指令重排。
在屏障前的操作,将会在屏障后的操作之前执行。

屏障场景作用
LoadLoad读1;屏障;读2读2执行前,读1要读取完毕
StoreStore写1;屏障;写2写2执行前,写1的结果要对其它线程可见
LoadStore读;屏障;写写执行前,要先读取完毕
StoreLoad写;屏障;读读执行前,写的结果要对其它线程可见(开销最大)

原理

使用 volatile 修饰变量,JVM 会为变量的操作前后插入屏障:

  • 写操作前插入 StoreStore 屏障,写操作后插入 StoreLoad 屏障。
  • 读操作前插入 LoadLoad 屏障,读操作后插入 LoadStore 屏障。

这很容易理解:屏障的作用就是隔开操作,本次操作之前的,就是与本次操作一致的两种操作形成的屏障;本次操作之后的,就是开头与本操作一致,以另一种操作结尾形成的屏障。

以刚才进程初始化的线程A为例:

volatile boolean finishInit = false;
// 初始化
init();
// 将初始化状态修改为 true
写之前:StoreStore 屏障
finishInit = true;
写之后:StoreLoad 屏障

总结

用 volatile 修饰变量,有 2 个作用:

  • 保证了变量在多线程中的可见性,取出的一定是最新值。(原理:先行发生原则)
  • 防止指令重排。(原理:内存屏障)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值