Volatile和“Happens-Before”原则.md

volatile不是Java独有的,C语言也有,它的直接目的就是禁用CPU缓存。
使用volatile修饰的变量,它实现的作用是,对于这个变量的读写,不能使用CPU缓存必须从内存中写入或读出。

看一个例子,线程A执行writer方法,线程B执行reader方法,那么输出x的值是多少呢:

public class VolatileApp {
    int x = 0;
    volatile boolean flag = false;

    public void writer(){
        x = 42;
        flag = true;
    }

    public void reader(){
        if(flag){
            System.out.println(x);
        }
    }

在1.5之前,可能是0也可能是42,在1.5之后,x的值等于42。
为啥呢,1.5之后对volatile语义进行了增强,增强了什么,就是一项“Happens-Before”原则。
“Happens-Before”原则:前面一个操作的结果对后面的操作是可见的。“Happens-Before”原则约束了编译器的优化行为,虽然允许优化,但是优化后必须遵守“Happens-Before”原则。这项原则可以拆解为6项

1.程序的顺序性规则

在一个线程中,按照程序顺序,前面的操作Happens-Before后续的任意操作。就是x = 42,Happens-Before ,flag= true。符合单线程的思维:
程序前面一个的修改对后面的操作是可见的。

2.volatile变量规则

对一个volatile变量的写操作,Happens-Before于后续对这个volatile变量的读操作。通常结合与第3条使用。

3.传递性

这项规则是说,如果A Happens-Before B, B Happens-Before C,那么A Happens-Before C。
结合上面给出的代码例子来看:

1.线程A “x = 42”,Happens-Before ,“flag= true” 符合规则1“顺序性规则”。
2.线程A的flag=true,Happens-Before ,线程B的if(flag),符合规则2
3.根据前两条的传递性,我们得到了 “x = 42”,Happens-Before ,if(flag)。符合规则3“传递性”。
这就是说操作“x = 42”是线程B可见的,线程B能看到x==42。
Java1.5版本的并发工具包就是靠volatile实现可见性的。

4.管程中锁的规则

指对一个锁的解锁 Happens-Before 后续对这个锁的加锁。
管程是一种通用的同步原语,在Java中就是synchronized,synchronized是Java对管程的实现。

管程在Java中的实现是隐式的,在进入同步块之前自动加锁,在同步块执行完之后会自动解锁,加锁以及解锁都是编译器帮我们实现的。
synchronized(this){
if(this.x <12){
x = 12;
}
}
结合这条规则解释,就是假设x=10,线程A执行了代码块之后,x=12然后自动释放锁。线程B进入到代码块是,能够看到线程A对x的操作,x==12.

5.线程start规则

这条是关于线程启动的,主线程A中启动子线程B,线程B能够看到在线程B启动前线程A的操作。
也就是说,线程A中调用线程B的start方法,那么该start操作Happens-Before 于线程B的任意操作。
Thread b = new Thread(){
public void run(){
// 此时x =77;
}
}
x =77;
b.start();

6.线程Join

指主线程A等待子线程B完成(线程A中,通过调用子线程B的join方法实现),当子线程B完成之(主线程A中join方法返回),主线程能够看到子线程的操作。
就是说,如果在线程A中,调用线程B的Join方法并成功返回,那么线程B中的任意操作 Happens-Before 于该join操作的返回。

Thread B = new Thread(()->{ 

    var = 66;//对共享变量的修改

}

B.start();

B.join();

//子线程所有对共享变量的修改,在主线程调用B.join之后可见。此时var==66;

你可能感兴趣的:(Java,并发编程)