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;
    }

}

 

 

 

posted @ 2019-10-23 22:42  向日葵班学霸  阅读(180)  评论(0)    收藏  举报