volatile 的内存语义

volatile 修饰的变量具有以下特性:

  • 可见性。对一个 volatile 变量的读,总是能看到任意线程对这个 volatile 变量最后的写入。
  • 原子性。对任意单个 volatile 变量的读/写具有原子性(除去 double 和 float 类型变量,因为这两种类型变量为 64 位类型)。

一个 volatile 变量的单个读/写操作,与锁对普通变量的读/写,它们之间的执行效果相同。

volatile 的 happens-before 规则:一个 volatile 的写 happens-before 于volatile 的读。

volatile 写–读建立的 happens-before 关系

	class ReorderExample {
	    int a = 0;
	    boolean flag = false;
	    
		public void writer() {
			a = 1;       // 1
			flag = true; // 2
		}
	
		public void reader() {
		    if(flag) {         // 3
		        int i = a * a; // 4
		    }
		}
	}

假设线程 A 执行 writer() 方法之后,线程 B 执行 reader() 方法。根据 happnes-before 规则进行分析:
1)根据程序次序规则,1 happens-before 2;3 happens-before 4。
2)根据 volatile 规则,2 happens-before 3。
3)根据 happens-before 的传递性规则,1 happens-before 4。

volatile 的内存语义

当写一个 volatile 变量时,JMM 会把该线程对应的本地内存中的共享变量值刷新到主内存。
当读一个 volatile 变量时,JMM 会把该线程对应的本地内存设置为无效。

volatile 的线程通信理解:
volatile 变量修改后,因为 volatile 的读总能看到 volatile 的写,在对 volatile 修饰变量进行写入操作时,本地内存中的共享变量进行修改后直接刷新到主内存;此时进行 volatile 读时,本地内存直接无效化,需要重新从主内存中获取。即 volatile 的一次写读就是线程之间进行一次通信,保证数据正确。

volatile 内存语义的实现

在每个 volatile 写操作的前面插入一个 StoreStore 屏障。 写操作执行前必须保证上面数据对其他处理器可见。
在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。写操作之后要保证数据刷新到主内存。
在每个 volatile 读操作的后面插入一个 LoadLoad 屏障。保证读操作比后续装载操作先装载数据,防止重排序导致数据执行错误。
在每个 volatile 读操作的后面插入一个 LoadStore 屏障。保证读操作比后续存储操作先装载数据,使后续存储操作获得到最新数据。

总结

读 volatile 操作通过将其他线程的本地内存无效化,强制从主内存中获取数据,达到数据实时更新效果。通过该方式,volatile 的写与读操作相当于线程间进行隐式通信。

引申一点,锁的内存语义与 volatile 一致,释放锁后数据必须对获取锁可见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值