Java内存模型
Java内存模型(JMM:Java memory model):
首先思考这样一个问题:有一个变量M = 2,线程A中执行:M = 3;现在,如何让线程B看到M = 3呢?
对于单线程的程序,无需考虑这样一个问题,因为B是在A执行操作之后才去读取M的值,肯定能看到M = 3,但是对于多线程的程序,线程B读取到的M值可能为2,为什么会出现这种现象呢,我们先分析下操作流程:
1、线程A:读取到M = 2
2、线程A :设置M = 3
线程B可以在1和2之间或者前后的任何位置去读取M的值;
这种情况就是线程无规则,自己任意并发执行的后果,那么这种执行情况又是怎么来的呢,接下来看下Java内存模型:

Java的内存模型分为主内存和工作内存,主内存就是所有的线程都可以访问到的公共的内存位置,工作内存则是线程私有的,只有本线程可以访问,其他线程访问不到。
那么,如果线程B想要看到M = 3,需要的步骤是:
线程A:1、从主内存读取M = 2,保存到自己的工作内存;
2、设置自己工作内存的M =3;
3、将主内存中的M更新为自己工作内存中的值:3;
这时候,线程B从主内存读取M的值,就是3了,也就是需要读取到上一个线程刷新到主内存中的最新的值,采用synchronized或者volatile都可以实现这一功能,也就是保证可见性(一个线程对共享变量的修改可以被其他的线程看到)。
另外,在没有充分同步的程序中,JMM还会使得不同的线程看到的某一个操作的执行顺序不同,通俗点讲,可以理解为,编译器和处理器为了优化性能,对代码的操作顺序进行了重排序,也就是所谓的指令重排序。
比如:
int m = 1;//第一步
int n = 2;//第二步
int j = m + n;//第三步
指令重排序后:
int n = 2;//第一步
int m = 1;//第二步
int j = m + n;//第三步
调整了代码的执行顺序,但是最终结果还是不变的。
现在,了解了指令重排序,再来分析下单利模式中的双重校验锁的方式:
public class SingleExample { private SingleExample(){}; private static SingleExample single = null; public static SingleExample getInstance(){ if(single == null){ //第一次检查single synchronized (SingleExample.class){ //加锁 if(single == null){ //第二次检查single single = new SingleExample();//操作single } } } return single; } }
看似没毛病的一个逻辑,其实是有隐藏的问题,这个隐患在single = new SingleExample()中;
这个操作可以分为三步:1、分配一块内存空间给SingleExample;
2、在内存空间上初始化SingleExample;
3、把内存空间的地址赋给single;
如果指令重排序,会出现什么情况呢:
1、分配一块内存空间给SingleExample;
2、把内存空间的地址赋给single;
3、在内存空间上初始化SingleExample;
如果重排序之后,线程执行到第2步,另一个线程正好来判断single == null为false,直接返回了一个还没有构建完成的对象,只有一个地址,啥也没有,这样就会造成不可预料的错误,那么如何避免呢,可以采用volatile来禁止指令重排序:
public class SingleExample { private SingleExample(){}; private static volatile SingleExample single = null; public static SingleExample getInstance(){ if(single == null){ //第一次检查single synchronized (SingleExample.class){ //加锁 if(single == null){ //第二次检查single single = new SingleExample();//操作single } } } return single; } }

浙公网安备 33010602011771号