java并发-Happens-Before规则

并发出现问题的根源或并发三要素

  1. 可见性
  2. 原子性
  3. 有序性(指令重排序主要影响该部分)

背景回顾

有序性:程序执行的顺序按照代码的先后顺序执行。

int i = 0;    //定义int变量           
boolean flag = false;  //定义布尔变量
i = 1;                //语句1  
flag = true;          //语句2

从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗? 不一定,为什么呢? 这里可能会发生指令重排序(Instruction Reorder)。 在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。 重排序分三种类型: 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。 内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。 从 java 源代码到最终实际执行的指令序列,会分别经历下面三种重排序: 上述的 1 属于编译器重排序,2 和 3 属于处理器重排序。这些重排序都可能会导致多线程程序出现内存可见性问题。对于编译器,JMM 的编译器重排序规则会禁止特定类型的编译器重排序(不是所有的编译器重排序都要禁止)。对于处理器重排序,JMM 的处理器重排序规则会要求 java 编译器在生成指令序列时,插入特定类型的内存屏障(memory barriers,intel 称之为 memory fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序(不是所有的处理器重排序都要禁止)。

什么是happens-before

JVM 规定了先行发生原则,让一个操作无需控制就能先于另一个操作完成。

为什么需要happens-before

JVM会对代码进行编译优化,会出现指令重排序情况,为了避免编译优化对并发编程安全性的影响,需要happens-before规则定义一些禁止编译优化的场景,保证并发编程的正确性。

有哪些happens-before规则

  1. 单一线程原则(Single Thread rule) 在一个线程内,在程序前面的操作先行发生于后面的操作。
  2. 管程锁定规则(Monitor Lock Rule) 一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。
  3. volatile 变量规则(Volatile Variable Rule)对一个 volatile变量的写操作先行发生于后面对这个变量的读操作。
  4. 线程启动规则(Thread Start Rule)Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。
  5. 线程加入规则(Thread Join Rule)Thread 对象的结束先行发生于 join() 方法返回。
  6. 线程中断规则(Thread Interruption Rule)对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。
  7. 对象终结规则(Finalizer Rule)一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始
  8. 传递性(Transitivity)如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,则操作 A 先行发生于操作 C。 A>B&B->C则A->C
经验分享 程序员 微信小程序 职场和发展