3

最近在看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值没有刷新到主存,如下图的情况:

clipboard.png

这就叫可见性问题。

在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变量的写操作之前。

未完待续~~~


jmyIpromiseu
7 声望2 粉丝