Java 知识 -- happens-before 原则

本文最后更新于:5 天前

JSR-133使用 happens-before 的概念来阐述操作之间的内存可见性。

在 JMM 中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在 happens-before 关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。

注意:

两个操作之间具有 happens-before 关系,并不意味着前一个操作必须要在后一个操作之前执行! happens-before 仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前(the first is visible to and ordered before the second)。

happens-before 部分规则

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。

    主要含义是:在一个线程内不管指令怎么重排序,程序运行的结果都不会发生改变。和as-if-serial 比较像。

  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。

    主要含义是:同一个锁的解锁一定发生在加锁之后

  • 管程锁定规则:一个线程获取到锁后,它能看到前一个获取到锁的线程所有的操作结果。

    主要含义是:无论是在单线程环境还是多线程环境,对于同一个锁来说,一个线程对这个锁解锁之后,另一个线程获取了这个锁都能看到前一个线程的操作结果!(管程是一种通用的同步原语,synchronized就是管程的实现)

  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。

    主要含义是:如果一个线程先去写一个volatile变量,然后另一个线程又去读这个变量,那么这个写操作的结果一定对读的这个线程可见。

  • 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。

  • start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。

    主要含义是:线程A在启动子线程B之前对共享变量的修改结果对线程B可见。

  • join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。

    主要含义是:如果在线程A执行过程中调用了线程B的join方法,那么当B执行完成后,在线程B中所有操作结果对线程A可见。

  • 线程中断规则:对线程interrupt方法的调用happens-before于被中断线程的代码检测到中断事件的发生。

    主要含义是:响应中断一定发生在发起中断之后。

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

一个happens-before规则对应于一个或多个编译器和处理器重排序规则。

as-if-serial 和 happens-before 原则

as-if-serial 和 happens-before 的主要作用都是:在保证不改变程序运行结果的前提下,允许部分指令的重排序,最大限度的提升程序执行的效率。

他们的区别是:前者只保证单线程下的有序性,而后者则是保证多线程下的程序执行有序性和可见性(多线程才有可见性一说,而且保证的前提是遵循了这些原则)

Java 中的有序性

  1. 如果在本地线程内观察,所有操作都是有序的(“线程内表现为串行”(Within-Thread As-If-Serial Semantics));

  2. 如果在一个线程中观察另一个线程,所有操作都是无序的(“指令重排序”现象和“线程工作内存与主内存同步延迟”现象)。

Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性:

  1. volatile关键字本身就包含了禁止指令重排序的语义

  2. synchronized则是由“一个变量在同一个时刻只允许一条线程对其进行lock操作”这条规则获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入