Happens-Before

1.什么是happens-before

happens-before:A happens-before B就是A先行发生于B(这种说法不是很准确),定义为hb(A, B)。在Java内存模型中,happens-before的意思是前一个操作的结果可以被后续操作获取。

2.有哪些happens-before规则

class Example {

    var x = 0

    @Volatile
    var v: Boolean = false

    fun write() {
        x = 42
        v = true
    }

    fun reader() {
        if (v) {
            println(x)
        }
    }

}

这里,假设线程A执行writer()方法,按照volatile会将v=true写入内存;线程B执行reader()方法,按照volatile,线程B会从内存中读取变量v,如果线程B读取到的变量v为true,那么,此时的变量x的值是多少呢??

这个示例程序给人的直觉就是x的值为42,其实,x的值具体是多少和JDK的版本有关,如果使用的JDK版本低于1.5,则x的值可能为42,也可能为0。如果使用1.5及1.5以上版本的JDK,则x的值就是42。

看到这个,就会有人提出问题了?这是为什么呢?其实,答案就是在JDK1.5版本中的Java内存模型中引入了Happens-Before原则。

接下来,我们就结合案例程序来说明Java内存模型中的Happens-Before原则。

【原则一】程序次序规则:在一个线程内一段代码的执行结果是有序的。就是还会指令重排,但是随便它怎么排,结果是按照我们代码的顺序生成的不会变。

例如【示例】中的程序x=42会在v=true之前执行。这个规则比较符合单线程的思维:在同一个线程中,程序在前面对某个变量的修改一定是对后续操作可见的。

【原则二】volatile变量规则:就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见。
【原则三】传递规则:如果A Happens-Before B,并且B Happens-Before C,则A Happens-Before C。

我们结合【原则一】、【原则二】和【原则三】再来看【示例】程序,此时,我们可以得出如下结论:

  • x = 42 Happens-Before 写变量v = true,符合【原则一】程序次序规则。
  • 写变量v = true Happens-Before 读变量v = true,符合【原则二】volatile变量规则。
    再根据【原则三】传递规则,我们可以得出结论:x = 42 Happens-Before 读变量v=true。

也就是说,如果线程B读取到了v=true,那么,线程A设置的x = 42对线程B就是可见的。换句话说,就是此时的线程B能够访问到x=42。

【原则四】锁定规则:对一个锁的解锁操作 Happens-Before于后续对这个锁的加锁操作。
public class Test{

   private int x=0;

    public void initx(){
        synchronized(this){  //自动加锁
            if(x<10){
                x=10
            }
        } //自动释放锁
    }
}

我们可以这样理解这段程序:假设变量x的值为0,线程A执行完synchronized代码块之后将x变量的值修改为10,并释放synchronized锁。当线程B进入synchronized代码块时,能够获取到线程A对x变量的写操作,也就是说,线程B访问到的x变量的值为10。

【原则五】线程启动规则:在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。

我们也可以这样理解:如果线程A调用线程B的start()方法来启动线程B,则start()操作Happens-Before于线程B中的任意操作。

fun main2(args: Array) {
        var x = 1
        //在主线程A中初始化线程B
        var thread = Thread {
            println("x=${x}")  //此处的变量值是多少了? 100
          
        }
        //主线程在启动子线程B之前 将变量修改为100
         x = 100
        thread.start()
    }

上述代码是在线程A中执行的一个代码片段,根据【原则五】线程的启动规则,线程A启动线程B之后,线程B能够看到线程A在启动线程B之前的操作,在线程B中访问到的x变量的值为100。

【原则六】线程终结规则:线程A等待线程B完成(在线程A中调用线程B的join()方法实现),当线程B完成后(线程A调用线程B的join()方法返回),则线程A能够访问到线程B对共享变量的操作。

也可以这样理解:在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。也称线程join()规则。

 fun main2(args: Array) {
        var x = 1
        var thread = Thread {
           x=100  //在线程B中将共享变量修改为100
        }
        thread.start() //启动线程
        thread.join() //等待线程执行完毕
        println(x)
    }

【原则七】线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。

例如,下面的程序代码。在线程A中中断线程B之前,将共享变量x的值修改为100,则当线程B检测到中断事件时,访问到的x变量的值为100。

   fun main2(args: Array) {
        var x = 1
        var thread = Thread {
            println("x=${x}")
            if (Thread.currentThread().isInterrupted) {
                println("interrupt x=${x}")

            }
        }
        thread.start()
        x = 100

        thread.interrupt()
        println(x)
    }

【原则八】对象终结原则:这个也简单的,就是一个对象的初始化的完成,也就是构造函数执行的结束一定 happens-before它的finalize()方法。

你可能感兴趣的:(Happens-Before)