赞
踩
Happens-Before(先行发生)原则是对Java内存模型(JMM)中所规定的可见性的更高级的语言层面的描述。用这个原则解决并发环境下两个操作之间的可见性问题,而不需要陷入Java内存模型苦涩难懂的定义中。关于Java内存模型中所规定的可见性定义本文不再叙述,感兴趣的读者可参考的书籍有《深入理解Java虚拟机》和《Java并发编程的艺术》。
Happens-Before原则最难以理解的地方在于如何理解"Happens-Before(先行发生)"这个词。我们以程序次序规则为例,“书写在前面的操作先行发生于书写在后的操作”,如果理解为“书写在前面的操作比书写在后面的操作先执行”看起来是没有什么问题的,写在前面的操作确实在程序逻辑上比写在后面的操作先执行。按照同样的理解,我们看一下管程锁定规则,“unlock操作先行发生于后面对同一个锁的lock操作”,如果理解为“unlock操作比同一个锁的lock操作先执行”这就很困惑了,还没有加锁,怎么解锁。
之所出现这种困惑的解读方式,是因为把“先行发生”理解为一种主动的规则要求了,而“先行发生”事实上是程序运行时出现的客观结果。正确的解读方式是这样的,对于“同一把锁”,如果在程序运行过程中“一个unlock操作先行发生于同一把锁的一个lock操作”,那么“该unlock操作所产生的影响(修改共享变量的值、发送了消息、调用了方法)对于该lock操作是可见的”。
按照这种理解,依次重新解读其他规则。
如下的这段代码开启了两个线程,updater和getter,updater线程将stop变量设置为true,getter线程死循环判断stop变量为true时结束循环。运行这段程序,得到以下输出。
updater set stop true.
getter线程未输出getter stopped
,说明updater线程对stop变量的修改对getter线程不可见。
public class WithoutMonitorLockRule { private static boolean stop = false; public static void main(String[] args) { Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); stop = true; System.out.println("updater set stop true"); } }, "updater"); Thread getter = new Thread(new Runnable() { @Override public void run() { while (true) { if (stop) { System.out.println("getter stopped"); break; } } } }, "getter"); getter.start(); updater.start(); } }
如下的这段代码定义了变量lockObject作为同步锁,运行这段程序,得到以下输出。
updater set stop true.
getter stopped.
该输出表明updater线程对stop变量的修改对getter线程是可见的。结合Happens-Before原则进行分析,根据程序次序规则在updater线程内stop = true
先行发生于lockObject锁的释放,在getter线程内lockObject锁的获取先行发生于if (stop)
;再根据传递性则stop = true
先行发生于if (stop)
,所以stop = true
对于if (stop)
是可见的。
public class MonitorLockRuleSynchronized { private static boolean stop = false; private static final Object lockObject = new Object(); public static void main(String[] args) { Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); synchronized (lockObject) { stop = true; System.out.println("updater set stop true."); } } }, "updater"); Thread getter = new Thread(new Runnable() { @Override public void run() { while (true) { synchronized (lockObject) { if (stop) { System.out.println("getter stopped."); break; } } } } }, "getter"); updater.start(); getter.start(); } }
ReentrantLock也可以起到和Synchronized关键字同样的效果,在Lock接口的注释中有如下描述。这段描述的意思是说所有的Lock接口实现必须在内存可见性上具有和内置监视器锁(Synchronized)相同的语义。
Memory Synchronization
All Lock implementations must enforce the same memory synchronization semantics as provided by the built-in monitor lock, as described in The Java Language Specification (17.4 Memory Model) :
A successful lock operation has the same memory synchronization effects as a successful Lock action.
A successful unlock operation has the same memory synchronization effects as a successful Unlock action.
使用ReentrantLock编写这段代码如下,同样可以实现和Synchronized相同的效果,具体原理在接下来的volatile变量规则中讨论。
public class MonitorLockRuleReentrantLock { private static boolean stop = false; private static ReentrantLock reentrantLock = new ReentrantLock(); public static void main(String[] args) { Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); reentrantLock.lock(); try { stop = true; System.out.println("updater set stop true."); } finally { reentrantLock.unlock(); } } }, "updater"); Thread getter = new Thread(new Runnable() { @Override public void run() { while (true) { reentrantLock.lock(); try { if (stop){ System.out.println("getter stopped."); break; } }finally { reentrantLock.unlock(); } } } }, "getter"); updater.start(); getter.start(); } }
如下的这段代码开启了两个线程,updater和getter,updater线程将stop变量设置为true,getter线程死循环判断stop变量为true时结束循环。运行这段程序,得到以下输出。
updater set stop true.
getter线程未输出getter stopped
,说明updater线程对stop变量的修改对getter线程不可见。
public class WithoutVolatileRule { private static boolean stop = false; public static void main(String[] args) { Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); stop = true; System.out.println("updater set stop true"); } }, "updater"); Thread getter = new Thread(new Runnable() { @Override public void run() { while (true) { if (stop) { System.out.println("getter stopped"); break; } } } }, "getter"); getter.start(); updater.start(); } }
使用volatile关键字修饰stop变量之后运行这段程序得到以下输出。
updater set stop true.
getter stopped.
说明updater线程对stop的修改对于getter线程可见。使用Happens-Before原则进行分析,根据volatile变量规则,updater线程对stop变量的写操作先行发生于getter线程对stop变量的读操作,所以updater线程将stop变量设置为true对getter线程读取stop变量是可见的。
public class VolatileRule { private static volatile boolean stop = false; public static void main(String[] args) { Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); stop = true; System.out.println("updater set stop true."); } }, "updater"); Thread getter = new Thread(new Runnable() { @Override public void run() { while (true) { if (stop) { System.out.println("getter stopped."); break; } } } }, "getter"); updater.start(); getter.start(); } }
volatile变量还有一个说是特点,其实也不是的特性。如下代码并未将stop变量用volatile修饰,而是用volatile修饰了volatileObject变量。运行这段代码将得到如下输出。
updater set stop true.
getter stopped.
public class VolatileRule1 { private static boolean stop = false; private static volatile Object volatileObject = new Object(); public static void main(String[] args) { Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); stop = true; volatileObject = new Object(); System.out.println("updater set stop true."); } }, "updater"); Thread getter = new Thread(new Runnable() { @Override public void run() { while (true) { Object volatileObject = VolatileRule1.volatileObject; if (stop) { System.out.println("getter stopped."); break; } } } }, "getter"); updater.start(); getter.start(); } }
上述结果表明虽然stop变量未被volatile修饰,但是它仍然在updater线程和getter线程之间可见,在未仔细品读Happens-Before原则之前,仅仅从java语法上来看是很神奇的。
结合Happens-Before原则的程序次序规则和传递性进行仔细分析。updater线程在将stop变量设置为true之后,又对volatileObject变量进行了赋值,而getter线程在读取stop变量之前首先读取了volatileObject。根据程序次序规则在updater线程内stop = true
先行发生于volatileObject = new Object()
,在getter线程内Object volatileObject = VolatileRule1.volatileObject
先行发生于if (stop)
;再根据传递性则stop = true
先行发生于if (stop)
,所以stop = true
对于if (stop)
就是可见的。
如此分析之后便发现这个特性并不神奇,仅仅是传递性在起作用罢了,对于其他Happens-Before原则这个特性同样存在,读者可自行验证。利用此特性的一个典型例子在jdk的java.util.concurrent.FutureTask
中,如下的代码节选自FutureTask
,在outcome后面的注释上写着non-volatile, protected by state reads/writes
,而state是被volatile所修饰的。这就是为什么在使用线程池的submit方法向线程池提交任务时,执行结果是可见的。而使用execute方法向线程池提交任务,这个任务所做的修改却不可见。读者可以自行验证。
private volatile int state;
/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes
上文提到的ReentrantLock对管程锁定规则的保证同样和volatile变量有关,在java.util.concurrent.locks.AbstractQueuedSynchronizer
类中有state
属性,该属性被volatile关键字修饰,不再赘述。
如下的代码首先开启了updater线程,updater线程调用getter.start
开启了getter线程,随后updater线程将stop设置为true,getter线程死循环判断stop变量为true时结束循环。运行该程序得到如下输出。
updater set stop true.
getter线程未输出getter stopped
,说明updater线程对stop变量的修改对getter线程不可见。
public class WithoutThreadStartRule { private static boolean stop = false; public static void main(String[] args) { Thread getter = new Thread(new Runnable() { @Override public void run() { while (true){ if (WithoutThreadStartRule.stop) { System.out.println("getter stopped."); break; } } } }, "getter"); Thread updater = new Thread(new Runnable() { @Override public void run() { getter.start(); Utils.sleep(1000); stop = true; System.out.println("updater set stop true."); while (true){ } } }); updater.start(); }
接下来对调一下updater线程中getter.start()
与stop = true
的位置,如下代码所示。运行该程序得到如下输出。
updater set stop true.
getter stopped.
该输出表明updater线程对stop变量的修改对getter线程是可见的。结合Happens-Before原则来分析一下,根据程序次序规则在updater线程内stop = true
先行发生于getter.start()
;而根据线程启动规则getter.start()
先行发生于该线程内的每一个动作(包括if (stop)
);再根据传递性规则stop = true
先行发生于if (stop)
,所以stop = true
对if (stop)
是可见的。
public class ThreadStartRule { private static boolean stop = false; public static void main(String[] args) { Thread getter = new Thread(new Runnable() { @Override public void run() { while (true){ if (stop) { System.out.println("getter stopped."); break; } } } }, "getter"); Thread updater = new Thread(new Runnable() { @Override public void run() { stop = true; System.out.println("updater set stop true."); Utils.sleep(1000); getter.start(); while (true){ } } }); updater.start(); } }
如下的这段代码开启了两个线程,updater和getter,updater线程将stop变量设置为true,getter死循环判断stop变量为true时结束循环。运行该程序得到以下输出。
updater set stop true.
getter线程未输出getter stopped
,说明updater线程对stop变量的修改对getter线程不可见。
public class WithoutThreadTerminationRule { private static boolean stop = false; public static void main(String[] args) throws InterruptedException { Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); stop = true; System.out.println("updater set stop true."); } }); Thread getter = new Thread(new Runnable() { @Override public void run() { while (true){ if (stop) { System.out.println("getter stopped."); break; } } } }, "getter"); updater.start(); getter.start(); } }
如下的代码getter线程在获取stop变量之前调用了updater.join()
等待updater线程结束。运行这段代码得到以下输出。
updater set stop true.
getter stopped.
该输出结果表明updater线程对stop变量的修改对getter线程是可见的。结合Happens-Before原则进行分析,根据线程终止规则updater线程中的所有操作(包括stop = true
)先行发生于getter线程调用updater.join()
等待updater结束;根据程序次序规则在getter线程内updater.join()
先行发生于if (stop)
;再根据传递性得出stop = true
先行发生于if (stop)
,所以stop = true
对if (stop)
是可见的。
public class ThreadTerminationRule { private static boolean stop = false; public static void main(String[] args) throws InterruptedException { Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); stop = true; System.out.println("updater set stop true."); } }); Thread getter = new Thread(new Runnable() { @Override public void run() { try { updater.join(); } catch (InterruptedException e) { } while (true){ if (stop) { System.out.println("getter stopped."); break; } } } }, "getter"); updater.start(); getter.start(); } }
如下的这段代码开启了两个线程,updater和getter,updater线程将stop变量设置为true,getter死循环判断stop变量为true时结束循环。运行这段代码得到以下输出。
updater set stop true.
getter线程未输出getter stopped
,说明updater线程对stop变量的修改对getter线程不可见。
public class WithoutThreadInterruptRule { private static boolean stop = false; public static void main(String[] args) { Thread getter = new Thread(new Runnable() { @Override public void run() { while (true) { if (stop) { System.out.println("getter stopped."); break; } } } }, "getter"); Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); stop = true; System.out.println("updater set stop true."); } }, "updater"); updater.start(); getter.start(); } }
如下的这段代码updater线程在将stop设置为true之后,调用了getter.interrupt()
方法,而getter线程在获取stop变量的值之前首先判断了Thread.currentThread().isInterrupted()
,运行这段代码得到以下输出。
updater set stop true.
getter stopped.
该输出结果表明updater线程对stop变量的修改对getter线程是可见的。根据Happens-Before原则进行分析,根据程序次序规则在updater线程内stop = true
先行发生于getter.interrupt()
,在getter线程内Thread.currentThread().isInterrupted()
先行发生于if (stop)
;根据线程中断规则getter.interrupt()
先行发生于Thread.currentThread().isInterrupted()
;再根据传递性得出stop = true
先行发生于if (stop)
,所以stop = true
对于if (stop)
是可见的。
public class ThreadInterruptRule { private static boolean stop = false; public static void main(String[] args) { Thread getter = new Thread(new Runnable() { @Override public void run() { while (true) { if (Thread.currentThread().isInterrupted()) { if (stop) { System.out.println("getter stopped."); break; } } } } }, "getter"); Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); stop = true; getter.interrupt(); System.out.println("updater set stop true."); } }, "updater"); updater.start(); getter.start(); } }
如下的代码中新建了一个Test对象,在Test对象的构造方法中将stop变量设置为true,随后将test变量赋值为null,则新建的Test对象成为垃圾,再在死循环中分配对象促使垃圾回收。运行该程序得到以下输出。
set stop true in constructor
stop true in finalize, threadName Finalizer
根据程序次序规则在main线程中stop = true
先行发生于Test的构造方法结束;根据对象终结规则Test的构造方法结束先行发生于Test的finalize()
方法的开始;再根据传递性得出stop = true
先行发生于finalize()
方法的开始,所以stop = true
对于finalize()
方法是可见的。
public class FinalizerRule { private static boolean stop = false; public static void main(String[] args) { Test test = new Test(); //test设置为null以后就可以回收了 test = null; while (true) { //促使垃圾回收 byte[] bytes = new byte[1024 * 1024]; } } static class Test { public Test() { stop = true; System.out.println("set stop true in constructor"); } @Override protected void finalize() throws Throwable { if (stop) { System.out.println("stop true in finalize, threadName " + Thread.currentThread().getName()); } else { System.out.println("stop false in finalize, threadName " + Thread.currentThread().getName()); } } } }
对象终结规则的反例特别难以复现,而对象终结规则在编程过程中又很难接触到,所以此处不再举例。
在第2节中很多例子的可见性保证都结合了程序次序规则和传递性,据此对前几条规则进行进一步解读如下。
对于该进一步解读,以管程锁定规则为例,如下代码并未将stop = true
及if (stop)
包括在syncronized代码块内,但是在updater线程内stop = true
在sychronized代码块之前,而在getter线程内if (stop)
在syncronized代码块之后。运行该程序得到以下输出。
updater set stop true.
getter stopped.
说明updater线程对stop的修改对getter线程是可见的。
public class MonitorLockRuleSynchronized1 { private static boolean stop = false; private static final Object lockObject = new Object(); public static void main(String[] args) { Thread updater = new Thread(new Runnable() { @Override public void run() { Utils.sleep(1000); stop = true; System.out.println("updater set stop true."); synchronized (lockObject) { } } }, "updater"); Thread getter = new Thread(new Runnable() { @Override public void run() { while (true) { synchronized (lockObject) { } if (stop) { System.out.println("getter stopped."); break; } } } }, "getter"); updater.start(); getter.start(); } }
Happens-Before原则在Java并发编程中的重要性不言而喻,不理解Happens-Before难以写出线程安全又高效的多线程代码。相比于Java内存模型Happens-Before原则在可见性的描述上要简单得多,但是仍然很拗口并难以琢磨。本文通过通俗化的语言并结合众多实例代码向读者展示了Happens-Before原则,希望对各位读者在理解Happens-Before原则时能有所帮助。
有任何问题均在可在公众号对话框中回复进一步交流。
关于作者
王建新,转转架构部服务治理负责人,主要负责服务治理、RPC框架、分布式调用跟踪、监控系统等。爱技术、爱学习,欢迎联系交流。
转转研发中心及业界小伙伴们的技术学习交流平台,定期分享一线的实战经验及业界前沿的技术话题。
关注公众号「转转技术」(综合性)、「大转转FE」(专注于FE)、「转转QA」(专注于QA),更多干货实践,欢迎交流分享~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。