深入Java并发编程(三):volatile原理
前言
我们在前一章了解到了volatile可以保证并发时的可见性和有序性,但对其原理未曾详述,本章就来谈谈volatile具体实现原理。
一、volatile如何保证变量的可见性
volatile变量在被修改后对其他线程是立即可以得知的。这是由这条规则决定的:当它被一个线程修改后会立即将新值写入主内存的变量里,如果此时有其他线程要使用这个变量,那么就会从主内存中重新读取volatile变量载入到工作内存中,如果不是要使用volatile变量,线程是不会重新读取的。而这条规则反映到底层细节处则是对诸多原子操作的规则,先用线程T,两个volatile变量V和W来说明:
1、关联线程T对变量V的read、load和use操作。也即是说,只有当T对V的前一个操作是load时,才能对V执行use操作,只有当T对V的后一个操作是use时,T对V才能执行load操作。这就保证了T每次使用V前都会从主内存中刷新V的值。
2、关联线程T对变量V的assign、store和write操作。道理和第一条一样。保证T每次修改完V的值都会立刻将它同步回主内存。
二、volatile如何保证变量的有序性
1、指令重排序对多线程并发执行结果的影响:
在讲volatile如何保证有序性前我们应该先了解变量的有序性是如何被指令重排序破坏的,请看下面的示例代码:
class Reorder {
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
}
}
}
OK,现在我们有一个线程A首先执行writer(),随之线程B执行reader() 。根据happens-before原则,我们能否判断操作1一定先行于操作4?,答案是不能(不过能根据程序次序规则确定3先行于4),所以操作1和操作4执行的次序是不确定的。因为指令重排序,所以会有多种次序关系。比如,执行顺序为:2->3->4->->1,这时a还未被赋值为1,就执行了i = a * a操作,因此得到的i是0。而执行顺序为1->2->3>->4时i的值就是1了。所以,我们可以确定指令重排序确实可以影响多线程的并发执行结果。
因此,volatile禁止指令重排序以此来实现有序性是有所依据的。
三、volatile为什么不能保证原子性呢
因为可见性无法保证运算的原子性。诚然,我们可以看到volatile保证了在对变量use操作前一定先执行了read和load操作。但是,当你通过use操作传入执行引擎后,在执行期间却不能保证这个变量的值是否还是有效的,因为其他线程很可能已经对它做了修改,所以我们需要对变量加锁来保证原子性。当然,也有些volatile适用的场景是不需要加锁的:对volatile变量的写操作不依赖于当前的值或者可以确保只有单一线程修改变量值,比如 volatile变量a,我直接给它赋值3,这时不论它有无在别的线程中被修改,在当前线程下的运行结果都是不被影响的。
上一篇: 关于变量转义的详细介绍
下一篇: PHP, 爬虫 PHP实现最简单爬虫原型
推荐阅读