所属文集:一起掌握并发
前奏
前文【保证可见性 之 禁止重排序】 中梳理清楚了一部分重排序对可见性的影响,同时也能感受到梳理清楚所有的细节真的是相当有难度的。程序员的视角来看更希望的是一些使用准则来保证可见性,而不是掌握所有底层的细节,而JMM理解我们的诉求,给我们提供了Happens-Before准则,我们遵循这些准侧来保证可见性。
Happens-Before概念
Java内存模型中指定的Happens-Before规则,Happens-Before规则最初是在一篇叫做Time, Clocks, and the Ordering of Events in a Distributed System的论文中提出来的,在这篇论文中,Happens-Before的语义是一种因果关系。在现实世界里,如果A事件是导致B事件的起因,那么A事件一定是先于(Happens-Before)B事件发生的,这个就是Happens-Before语义的现实理解。
在Java语言里面,Happens-Before的语义本质上是一种可见性,A Happens-Before B 意味着A事件对B事件来说是可见的,无论A事件和B事件是否发生在同一个线程里。例如A事件发生在线程1上,B事件发生在线程2上,Happens-Before规则保证线程2上也能看到A事件的发生
Java 原生存在的Happens-Before规则:
这些是Java 内存模型下存在的原生Happens-Before关系,无需借助任何同步器协助就已经存在,可以在编码中直接使用。
-
程序次序规则(Program Order Rule) 在一个线程内,按照程序代码顺序,书写在前面的操作Happens-Before书写在后面的操作
-
管程锁定规则(Monitor Lock Rule) An unlock on a monitor happens-before every subsequent lock on that monitor. 一个unlock操作Happens-Before后面对同一个锁的lock操作。思考,不是同一个锁就不保证了吗?
synchronized (this) { //此处自动加锁
// x是共享变量,初始值=10
if (this.x < 12) {
this.x = 12;
}
} //此处自动解锁
//管程中锁的规则,可以这样理解:
//假设x的初始值是10,线程A执行完代码块后x的值会变成12(执行完自动释放锁),
//线程B进入代码块时,能够看到线程A对x的写操作,也就是线程B能够看到x==12
- volatile变量规则(volatile Variable Rule) A write to a volatile field happens-before every subsequent read of that volatile. 对一个volatile变量的写入操作Happens-Before后面对这个变量的读操作。
- 线程启动规则(Thread Start Rule) Thread对象的start()方法Happens-Before此线程的每一个动作。
Thread B = new Thread(()->{
// 主线程调用B.start()之前
// 所有对共享变量的修改,此处皆可见
// 此例中,var==77
});
// 此处对共享变量var修改
var = 77;
// 主线程启动子线程
B.start();
- 线程终止规则(Thread Termination Rule) 线程中的所有操作都Happens-Before对此线程的终止检测。
Thread B = new Thread(()->{
// 此处对共享变量var修改
var = 66;
});
// 例如此处对共享变量修改,
// 则这个修改结果对线程B可见
// 主线程启动子线程
B.start();
B.join()
// 子线程所有对共享变量的修改
// 在主线程调用B.join()之后皆可见
// 此例中,var==66
- 线程中断规则(Thread Interruption Rule) 对线程interrupt()方法的调用Happens-Before被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupt()方法检测到是否有中断发生。
- 对象终结规则(Finalizer Rule) 一个对象的初始化完成(构造函数执行结束)Happens-Before它的finalize()方法的开始。
- 传递性(Transitivity) 偏序关系的传递性:如果已知hb(a,b)和hb(b,c),那么我们可以推导出hb(a,c),即操作a Happens-Before 操作c。
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
// 这里x会是多少呢?
}
}
}
根据程序次序规则 + volatile变量规则+传递性,我们得到结果:“x=42” Happens-Before 读变量“v=true”。这意味着什么呢?
image.png
Java语言中无需任何同步手段保障就能成立的先行发生规则就只有上面这些了。
还存在其它的Happens-Before吗
Java中原生满足Happens-Before关系的规则就只有上述8条,但是还可以通过它们推导出其它的满足Happens-Before的操作,如:
- 将一个元素放入一个线程安全的队列的操作Happens-Before从队列中取出这个元素的操作
- 将一个元素放入一个线程安全容器的操作Happens-Before从容器中取出这个元素的操作
- 在CountDownLatch上的倒数操作Happens-Before CountDownLatch#await()操作
- 释放Semaphore许可的操作Happens-Before获得许可操作
- Future表示的任务的所有操作Happens-Before Future#get()操作
- 向Executor提交一个Runnable或Callable的操作Happens-Before任务开始执行操作
- 如果两个操作之间不存在上述的Happens-Before规则中的任意一条,并且也不能通过已有的Happens-Before关系推到出来,那么这两个操作之间就没有顺序性的保障,虚拟机可以对这两个操作进行重排序!
如果存在hb(a,b),那么操作a在内存上面所做的操作(如赋值操作等)都对操作b可见,即操作a影响了操作b。
网友评论