java并发-Happens-Before规则
并发出现问题的根源或并发三要素
- 可见性
- 原子性
- 有序性(指令重排序主要影响该部分)
背景回顾
有序性:程序执行的顺序按照代码的先后顺序执行。
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规则
- 单一线程原则(Single Thread rule) 在一个线程内,在程序前面的操作先行发生于后面的操作。
- 管程锁定规则(Monitor Lock Rule) 一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。
- volatile 变量规则(Volatile Variable Rule)对一个 volatile变量的写操作先行发生于后面对这个变量的读操作。
- 线程启动规则(Thread Start Rule)Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。
- 线程加入规则(Thread Join Rule)Thread 对象的结束先行发生于 join() 方法返回。
- 线程中断规则(Thread Interruption Rule)对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。
- 对象终结规则(Finalizer Rule)一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始
- 传递性(Transitivity)如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,则操作 A 先行发生于操作 C。 A>B&B->C则A->C
