Java内存模型简单介绍

         说到Java内存模型可能一开始就会让人想到Java虚拟机里面的数据区的概念,但是这里的Java内存模型(JMM,Java memory model)是指用Java语言规范定义的内存模型,它是用在线程间数据的可见性上的定义,在Java中数据会存在所见非所得的情况,也就是说你在程序上面看上去会得到想要的结果,但是现实是运行出来的结果往往并不是自己想要的,如下的一个例子(直接敲的代码可能不正确,大概意思是这样)

public class TestSee{
     boolean canSee = true;

     public static void main(String [] args){
           TestSee testSee = new TestSee();
           new Thread(() -> {
               int i = 0;
               while(testSee.canSee){
                    i++;
               }
               System.out.println(i);
          }).start();

         Thread.sleep(3000);
         testSee.canSee = false;
     }
}

      这个通过启动一个子线程来判断canSee为true的时候一直运行,然后整个主线程休眠3秒过后,将canSee赋值为了false,我们想要的效果就是在改为false后子线程也退出了程序的运行,但是在不同的参数情况下子线程有可能是没有办法退出的,是因为它可能并不知道canSee已经修改为false,造成这个的原因可能是高速缓存引起的,为什么会是高速缓存呢?这个就需要去了解下Java的解释执行和编译执行了,当Java运行一定时间的解释执行时因为程序没有任何变化,为了更好的节约内存资源,就会通过指令重排的方式通过JIT(just in time complier)的方式进行编译执行,将代码进行缓存起来,也会就会出现下面这样的伪代码

boolean see = canSee;
while(true){
   i++;
}

 所以后面不管canSee是否改变了有可能都不能得到正确的响应了。

        那么我们可以通过什么方式来防止缓存呢?答案就是使用volatile关键字了,使用了这个关键字后虚拟机就会自动禁用高速缓存和指令重排了,如果改变了使用volatile关键字修饰的变量,在很短暂的时间会通过Java将变量的工作线程和主线程的修改值进行传递来及时的响应值的变化,从而达到线程间数据可见性的,同时通过volatile关键字修饰的变量会有以下几个特征:

      1.变量的修改会对后面所有的其他线程可见

      2.如果变量的修改A动作发生在动作B之前,动作B又发生在动作C之前,那么变量的修改A动作也会对C动作产生可见性

      3.如果线程t1成功执行了线程t2.join()方法,那么t1的所有操作对t2都可见

     上面的这些特征也叫做happen-before原则,因此如果对于一个公共变量来说,如果需要经常修改而又有其他线程对其引用,最好就使用volatile关键字进行修饰。

      对于数据的可见性同步保证除了使用volatile关键字以外,还有以下几种情形:

      1.使用加锁/解锁进行控制,它不会产生指令重排

       2.线程启动时和该线程中的第一个执行的命令也一定是同步保证的

       3.子线程的最后操作与主线程是同步保证的

       4.如果在主线程中通过interrupt方式中断了子线程的执行,那么这中断操作也会对其他子线程可见,当然这里也并不一定是主线程执行中断操作,只有其他子线程可以使用到中断子线程的实例都可以执行中断操作。

       在使用的过程中对应不变的变量最好使用final来修饰,保证对所有线程的可见及不可修改,同时最好不要修改共享的byte[]数组,这个会带来数据一致性的问题,volatile关键字对64位的共享值(double,long)也更好一些。

     

猜你喜欢

转载自www.iteye.com/blog/357029540-2443704