为了深入了解并行机制,java内存模型JMM是很有必要了解的。JMM的关键技术点都是围绕着多线程的原子性、可见性和有序性来建立的。
原子性:是指一个操作不可中断。在多个线程执行时,一个操作一旦开始,就不会被其他线程干扰。
可见性:是指当一个线程修改了某一个共享变量的值时,其他线程是否可以立即知道这个修改。
有序性:对于一个程序而言,总是习惯认为代码从前往后依次执行的。但是在并发时,程序执行可能会出现乱序,程序在执行时,可能会出现指令重排,重排后的指令与原指令的顺序未必一致。
tips:指令重排序有一个前提,就是保证串行语义一致。
指令重排序的主要原因就是,为了充分利用CPU,利用流水线技术可以使CPU高效执行。
Happen-Before规则:
java虚拟机和执行系统会对指令进行一定的重排,但是重排也是有原则的,这些原则是指令重排不可违背的:
- 程序顺序原则:一个线程内保证语义的串行性。
- volatile规则:volatile变量的写要先于读发生,这也就保证volatile变量的可见性。
- 锁规则:解锁必然发生在随后的加锁前。
- 传递性:A先于B,B先于C,那么A必然先于C。
- 线程的start()方法先于它的每一个动作。
- 线程的所有操作先于线程的终结。
- 线程的中断先于被中断的代码。
- 对象的构造函数的执行、结束先于finalize()方法。
volatile关键字
查询英文字典,你会发现volatile关键字的含义是,易变得、不稳定的。当你使用volatile关键字声明一个变量时,就等于告诉虚拟机,这个变量极有可能被某一个线程修改。为了确保变量被修改后,所有线程可以看到改动,虚拟机会采取一些措施,保证变量的可见性等特点。
关键字volatile对于保证操作的原子性是有很大帮助,但是volatile不能代替锁,也无法保证复合操作的原子性。
关键字volatile是可以保证数据的可见性额有序性。
双重校验锁中volatile的使用
public class Singleton {
private volatile static Singleton Singleton;
private Singleton() {
}
public Singleton getInstance() {
if (null == Singleton) {
synchronized (Singleton.class) {
if (null == Singleton) {
Singleton = new Singleton();
}
}
}
return Singleton;
}
}
当线程A进入getInstance()方法时,先判断Singleton是否为空,进入synchronized方法块,线程B进入getInstance()方法时,线程A占有锁,此时线程B阻塞,当A返回Singleton实例对象时,退出锁,B获得锁,进入synchronized方法块,此时Singleton不为空,直接返回。
正因为Singleton有volatile修饰,可以实现线程间数据共享,以及指令重排序。使得线程B可以获得Singleton的对象。如果不加volatile,由于指令重排序,在单核或者多核处理器上多个线程间无法及时判断。