Java并发机制(2)--volatile关键字

关键字volatile提供了轻量级的同步机制,在理解volatile关键字之前先来看几个支撑技术。

有序性的一种保证方式:内存屏障

内存屏障是一组指令,分4类,这些指令指定了其前后指令的排序规则。

屏障类型指令示例说明
LoadLoad BarrierLoad1;LoadLoad;Load2确保load1数据的装载先于load2及其后续所有load指令
StoreStore BarrierStore1;StoreStore;Store2确保Store1数据刷新到内存先于Store2及其后续所有store指令
LoadStore BarrierLoad1;LoadStore;Store2确保load1数据的装载先于Store2及其后续所有store指令
StoreLoad BarrierStore1;StoreLoad;Load2确保Store1数据刷新到内存先于load2及其后续所有load指令 。注意:StoreLoad Barriers会使该屏障之前的所有内存访问指令(包括load和store指令)都执行完了之后,再执行屏障之后的内存访问指令。

也就是说,在一个指令序列中插入了内存屏障,这些内存屏障会限制其前后指令的排序,而在两个内存屏障之间代码序列的重排序则没有约束。就好像屏障一样,被限制的指令无法越过。
同时也可以看出内存屏障将缓存值刷新到内存也起到了提供可见性的作用。

强大的汇编指令前缀:Lock

lock前缀有三方面的功能:
1. 确保对内存的读-改-写操作原子执行。
2. 禁止该指令,与之前和之后的读写指令重排序。
3. 将写缓冲区的所有数据刷新到内存中,并且这个写操作会使其他CPU里缓存了该内存地址的数据无效。

其中第2条和第3条共同起到了内存屏障的效果,并且是一并禁止了load和store指令。
可以看出,单单一个lock前缀就提供了原子性、有序性和可见性的三方面的可靠保证。

volatile修饰变量

被volatile修饰的变量有三个特性:
1. 原子性。对任意单个volatile变量的读、写具有原子性,但类似于volatile++这种复合操作没有原子性。
2. 可见性。对一个volatile的读,总是能看到任意线程对这个变量的最后一次写入。
3. 有序性。volatile禁止指令重排序。

之所以volatile具有这三个特性,在于对volatile变量的操作有lock prefix支持。lock prefix提供了三方面的特性保证。

锁与volatile

锁的释放与volatile写具有同样的内存语义:释放锁和对volatile变量的写,JMM都会把线程对应的本地内存中的共享变量刷新到主内存中。
锁的获取与volatile读具有同样的内存语义:获取锁和对volatile变量的读,JMM都会将线程对应的本地内存置为无效,而是从主内存中重新读取。

CAS与volatile

CAS操作具有volatile读和写的内存语义。该操作在执行cmpxchg指令之前,如果是多处理器,将会插入lock前缀。lock前缀提供了与volatile一致的内存语义。

volatile重排序例子

//线程1:
context = loadContext();   //语句1
inited = true;             //语句2

//线程2:
while(!inited ){
  sleep()
}
doSomethingwithconfig(context);

有可能语句2会在语句1之前执行,那么就可能导致context还没被初始化,而线程2中就使用未初始化的context去进行操作,导致程序出错。

这里如果用volatile关键字对inited变量进行修饰,就不会出现这种问题了,因为当执行到语句2时,必定能保证context已经初始化完毕。

现代操作系统
Java并发编程的艺术
深入理解Java虚拟机
Java并发编程实战
JSR-133
并发编程网
Intel IA-32

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值