1.重排序
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排。
指令重排一般分为以下三种:
- 编译器优化重排编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
- 指令并行重排现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行的语句的结果),处理器可以改变语句对应的机器指令的执行顺序。
- 内存系统重排由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,导致内存与缓存的数据同步存在时间差。
指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。所以在多线程下,指令重排序可能会导致一些问题。
2.数据竞争与顺序一致性模型
2.1 数据竞争
数据竞争:未正确同步的程序因为对同一资源竞争导致最终结果的不确定性。
Java内存模型(JMM)对于正确同步多线程程序的内存一致性做了以下保证:
如果程序是正确同步的,程序的执行将具有顺序一致性。 即程序的执行结果和该程序在顺序一致性模型中执行的结果相同。
这里的同步包括了使用volatile、final、synchronized等关键字来实现多线程下的同步。
如果程序员没有正确使用volatile、final、synchronized,那么即便是使用了同步(单线程下的同步),JMM也不会有内存可见性的保证,可能会导致你的程序出错,并且具有不可重现性,很难排查。
2.2 顺序一致性模型
顺序一致性内存模型是一个理想化的理论参考模型,它为程序员提供了极强的内存可见性保证。
顺序一致性模型有两大特性:
- 一个线程中的所有操作必须按照程序的顺序(即Java代码的顺序)来执行。
- 不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序。即在顺序一致性模型中,每个操作必须是原子性的,且立刻对所有线程可见。
总结:
由于顺序一致性模型中的每个操作必须立即对任意线程可见。不论程序是否同步,所有线程都智能看见同一个操作执行顺序,但是同步与不同步操作顺序是不一样的,顺序一致性模型下的程序不论是否同步都具有结果一致性,因为其操作顺序是确定的。
但是JMM不保证每个操作对任意线程可见,所以JMM只能保证正确同步的程序具有结果一致性。对于未同步的多线程程序,JMM只提供最小安全性:线程读取到的值,要么是之前某个线程写入的值,要么是默认值,不会无中生有。
JMM保证了单线程情况下与正确同步的多线程程序有结果一致性,并且在临界区内(同步块或同步方法中)的代码可以发生重排序(但不允许临界区内的代码“逃逸”到临界区之外,因为会破坏锁的内存语义);JMM没有保证未同步程序的执行结果与该程序在顺序一致性中执行结果一致。因为如果要保证执行结果一致,那么JMM需要禁止大量的优化,对程序的执行性能会产生很大的影响。
3 happens-before原则
是一个给程序员使用的规则,只要程序员在写代码的时候遵循happens-before规则,JVM就能保证指令在多线程之间的顺序性符合程序员的预期。
happens-before关系的定义如下:
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
- 两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么JMM也允许这样的重排序。
如果操作A happens-before操作B,那么操作A在内存上所做的操作对操作B都是可见的,不管它们在不在一个线程。
happens-before原则保证了正确同步的多线程程序具有最终结果一致性。
具体原则:
- 单线程的happens-before原则:在同一个线程中,写在前面的操作happens-before写在后面的任意操作。
- volatile的happens-before原则:对于一个volatile变量的写操作happens-before对此变量的读写操作。
- 锁的happens-before原则:同一个锁的解锁(unlock)操作happens-before对此变量的加锁(lock)操作。
- happens-before的传递性原则:若A操作 happens-before B操作,B操作 happens-before C操作,则A操作 happens-before C操作。
- 线程启动的happens-before原则:同一个线程的start方法happens-before此线程的其他方法。
- 线程中断的happens-before原则:对一个线程的interrupt方法的调用happens-before被中断线程检测到中断发送的代码。
- 线程终结的happens-before原则:线程中的所有操作happens-before线程的终止检测。
- 对对象创建的happens-before原则:一个对象的初始化操作happens-before对该对象finalize方法的调用。