首先了解一下什么是JMM,JMM是Java内存模型,它规定和指引Java程序在不同的内存架构,CPU和操作系统间有确定性行为。它在多线程的情况下尤其重要。Java内存模型对一个线程所作的变动能被其他可见提供了保证,它们之间是先行发生关系。
JSR-133内存模型规范使用Happens-before的概念来阐述操作之间的内存可见性。在JVM中如果一个操作执行的结果需要对另外一个操作可见,那么这两个操作之间必须要存在Happens-Before关系。Happens-Before规则:
- 程序顺序规则:一个线程中的每个操作发生在该线程中的任意后续操作之前
- 监视器锁规则:对一个监视器的解锁操作发生在随后的加锁操作之前
- volatile变量规则:对一个volatile变量的写发生在后续任意对这个变量的读操作之前
- 传递性规则:如果A happens-before于B,B happens-before于C,那么A happens-before于C
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
内存屏障指令:用于禁止特定类型的指令重排序,不让编译器优化
指令重排序的示例:
在DCL单例模式中会用volatile来修饰单例变量。singleton = new Singletion();这句话的执行顺序是:分配内存,初始化,对象指向内存空间。编译器优化,指令重排序后是:分配内存,对象指向内存空间,初始化。如果该instance中有成员变量,那么先指向内存空间再初始化会导致成员变量没有初始化,只是instance初始化了,也就是instance只部分初始化了,虽然拿不到多个实例,但是也拿不到整个实例。所以用volatile修饰后编译后的代码中会有lock前缀指令,lock前缀指令实际上相当于一个内存屏障,内存屏障会提供3个功能:1. 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;2. 它会强制将对缓存的修改操作立即写入主存; 3. 如果是写操作,它会导致其他CPU中对应的缓存行无效。