最近在看java多线程相关,看到这篇来自大神Jakob Jenkov关于Volatile关键字的讲解感觉非常详细易懂,特此转载一下。
原文链接:http://tutorials.jenkov.com/j...
内存可见性问题
在多线程应用中,对于每个非Volatile变量,每个cpu会从内存中拷贝一份副本到cpu的高速缓存中。
假设一种场景,两个或两个以上的线程去访问共享对象SharedObject
public class SharedObject {
public int counter = 0;
}
线程1去写counter值,线程1和线程2会不定期的读counter的值,则有可能存在两个cpu中缓存的counter值不一样,cpu1写入到缓存中的counter值没有刷新到主存,如下图的情况:
这就叫可见性问题。
在java中,Volatile关键字可以保证变量的可见性。
如果对counter增加Volatile声明:
public class SharedObject {
public volatile int counter = 0;
}
则所有线程对counter的写都会立即刷新到主存中,而且所有对counter的读也都直接从主存中去读。
Volatile关键字的可见性保证不仅限于被Volatile声明的变量本身
还有以下两种变量即使没有用volatile声明,也可以得到相应的可见性保证
- 如果线程A去写一个volatile变量,随后线程B去读这个volatile变量,则所有在线程A写这个volatile变量之前对线程A可见的变量,在线程B读这个volatile变量之后也对线程B可见。
- 如果线程A去读一个volatile变量,则所有对线程A可见的变量,在线程A读取这个volatile变量时,会从主存中重新读一次
晦涩难懂,举个栗子:
解释第一条:
public class MyClass {
private int years;
private int months
private volatile int days;
public void update(int years, int months, int days){
this.years = years;
this.months = months;
this.days = days;
}
}
当days变量被写入时,years和months因为对当前线程可见,也会被写入到主存中。
解释第二条:
ublic class MyClass {
private int years;
private int months
private volatile int days;
public int totalDays() {
int total = this.days;
total += months * 30;
total += years * 365;
return total;
}
public void update(int years, int months, int days){
this.years = years;
this.months = months;
this.days = days;
}
}
totalDays()方法执行时,第一步读取days的值,因为days时volatile变量,所以所有对当前线程可见的变量都会从主存中重新读一次。
但是这样对带来一个由重排序而产生的新问题
为了提高性能,CPU和JVM都会指令在不影响语义的前提下进行重排序操作,举个栗子:
上面的update方法可能会被重排序成:(为了方便理解,将参数名加了个new后缀)
public class MyClass {
private int years;
private int months
private volatile int days;
public void update(int years, int monthsNew, int daysNew){
this.days = days;
this.months = monthsNew;
this.years = daysNew;
}
}
这种情况下虽然在days被写入的时候,years和months也会被刷新到主存中,但是并不是monthsNew和daysNew的值,这就意味着,这种重排序改变了语义。
重排序问题的解决方案:volatile的 Happens-Before 原则
为解决这个问题,java赋予了volatile变量一些 Happens-Before 原则的保证。
原则如下:
- 对其它变量的读和写操作不能被重排序到对一个volatile变量的写操作之后。
- 对其他变量的读和写操作不能被重排序到对一个volatile变量的读操作之前。
注意:两条原则反过来是不保证的,比如对其它变量的读和写操作有可能会被重排序到对一个volatile变量的写操作之前。
未完待续~~~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。