我们一般将Java并发常见的happens-before原则归为八类,其中很多都是我们非常熟悉的,只是我们很少从happens-before的角度去理解它。下面我们来分别介绍这八个原则。
它是最简单的happens-before规则,就是说在单个线程内前面的代码happens-before于后面的代码。比如下面的例子,在主线程内step-1比step-2输出更早,四个输出操作按照代码顺序执行。
是指某个锁解锁前的操作happens-before于接下去获取该锁后的其它操作。以synchronized锁为例,我们都知道进入和离开synchronized大括号就是加锁和解锁操作,假如线程一先获取锁,则在解锁前的所有操作都happens-before于线程二获取该锁后的其它操作。反之亦成立。
该例子可能输出以下两种情况,因为happens-before原则保证了可见性。第一种情况是线程一解锁前的操作对线程二获取锁后的操作可见,所以线程一的x=3而线程二的x=7。第二种情况是线程二解锁前的操作对线程一获取锁后的操作可见,所以线程二的x=4而线程一的x=7。
是指对某个volatile变量的写操作和写操作之前的所有操作都happens-before于对这个volatile变量的读操作和读操作之后的所有操作。比如下面例子中,x是volatile变量而y为非volatile变量。线程一对y和x进行写操作,那么线程二在对x和y读操作时就能看到x和y的最新值,即输出“thread2 x,y = 4,2”。这里x写操作前的所有操作都对x读操作后的所有操作可见。
是指某个线程在调用另外一个线程的start方法前的所有操作都happens-before于刚被启动的线程中的所有操作。看下面的例子,主线程中先执行x=3,然后创建thread1并调用它的start方法。运行后输出为“thread1 x = 6”,调用start方法前的x=3在线程一中是可见的。
是指如果线程A调用了线程B的join方法,那么线程B的所有动作都happens-before于线程A中join方法后面的所有动作。如下例子中,主线程启动线程一后并调用join方法等待线程一执行完才返回,那么线程一中的x=x+2操作对主线程中join后面可见,所以输出结果为“main-thread x = 2”。
是指线程A调用了线程B的interrupt方法,那么线程A在调用interrupt方法之前的所有动作都happens-before于线程B中被interrupt后的所有动作。如下程序中,线程一启动后开始睡眠,主线程执行x=x+2后睡眠两秒,然后调用线程一的interrupt方法,线程一被中断后输出“thread1 x = 2”。
是指对象的所有操作都happens-before于该对象的finalize方法。如下代码中,创建VisibilityDemo10对象后对其属性进行修改,然后销毁该对象。调用System.gc()能使该对象被垃圾回收器回收,回收前会调用finalize方法,该方法输出“finalize方法 x = 4”。
即是指如果A happens-before于B,且B happens-before于C,那么A就happens-before于C。看下面的例子,主线程中的x=3操作happens-before于线程一中所有操作,线程一中的x=x*2操作happens-before于线程二中的所有操作,最终输出为“thread2 x = 12”。由传递原则可以知道,主线程中的thread1.start()之前的操作happens-before于线程二中的所有操作。
更多Java并发原理可关注作者下面的专栏:
作者简介:笔名seaboat,擅长人工智能、计算机科学、数学原理、基础算法。出版书籍:图解数据结构与算法、Tomcat内核设计剖析、图解Java并发原理、人工智能原理科普。