1.1.6 JVM内存模型详解

目录

一、什么是Java内存模型?

二、Java内存模型 vs JVM运行时数据区

三、了解CPU的指令重排

四、可见性问题

五、volatile关键字

六、线程间操作的定义

七、同步的规则定义

八、Happens-before先行发生原则

九、final 在JVM 中的处理

十、Word Tearing 字节处理

十一、double 和 long 的特殊处理


一、什么是Java内存模型?

       Java内存模型描述了多线程的执行规则。

二、Java内存模型 vs JVM运行时数据区

       

 

三、了解CPU的指令重排

        Java编程语言的语义允许Java编译器和微处理器进行执行优化,这些优化就导致了与其交互的代码不再同步,

        从而导致看似矛盾的行为。

    比如说:

while(isRuning){
   i++;
}

    上述这段代码,在字节码中,会先得到  isRuning 的值——> 判断 isRuning 的值——>  i++ ——>跳回第一行。

四、可见性问题

      什么是可见性问题?

      有一个值存在于内存中,有一条线程对该值进行写的操作,另一条线程进行读取却读不到;这就是可见性问题。

     

 请看如下代码,最终控制台上会打印什么样的信息??

public class Test {

    int i = 0;

    boolean isRunning = true;

    public static void main(String[] args) throws Exception {
        Test test = new Test();
        new Thread(() -> {
            while (test.isRunning) {
                test.i++;
            }
            System.out.println("i==" + test.i);
        }).start();
        LockSupport.parkNanos(1000 * 1000 * 1000 * 3L);
        test.isRunning = false;
        System.out.println("shutdown....");
    }

}

     通过执行实例代码,我们可以发现,控制台只打印了一句:shutdown...

     为什么呢?这就涉及了可见性问题(以及指令重排)。

     因为这涉及到了CPU的缓存,CPU共有三层缓存,这里,只讲CPU的高速缓存;

     问题分析:

    1、CPU的高速缓存:

         主线程休眠3秒前,次线程读取到的 isRuning=true,此时进入循环体,等到主线程修改

         isRuning=false时,该值还是处在CPU的高速缓存中,没有及时更新到堆内存;所以导致

         次线程读取的 isRuning 还是 true。

        反过来分析也是一样的,就是说主线程已经更新了值,但次线程读取的还是当前线程在CPU高速缓存中的值。

        或者说,主线程修改的值还在高速缓存中,同时次线程读取的也是告诉缓存中的值。

    2、JVM的指令重排

         while循环体的代码会被指令重排,重排后代码如下:

       boolean f = test.isRunning;
       if (f){
            while(true){
                test.i++;
            }
        }

     整体代码分析图解:

五、volatile关键字

       可见性问题:让一个线程对共享变量的修改,能够及时的被其它线程看到。

      根据JMM规定的happen before 和同步原则:

      对于某个 volatile 字段的写操作 happens-before 每个后续对该 volatile 字段的读操作;

      对 volatile 变量 v 的写入,与所有其他线程后续对 v 的读同步。

      也就是说,对一个变量的修改,在后续的访问中,一定能读取到正确的值。

      

      要满足这些条件,所以 volatile 关键字就有这些功能:

      1、禁止缓存:

           volatile 变量的访问控制符会加个 ACC_VOLATILE

           https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5

      2、对 volatile 修饰的变量相关的指令不做重排序。

六、线程间操作的定义

     1、线程间操作是指:一个程序执行的操作可被其它线程感知,或被其它线程直接影响;

     2、Java内存模型只描述线程间操作,不描述线程内操作,线程内操作按线程内的语义执行。

     

七、同步的规则定义

  • 对于对 volatile 变量 v 的写入,与所有其它线程后续对 v 的读同步;
  • 对于监视器 m 的解锁与所有后续操作对于 m 的加锁同步;
  • 对于每个属性写入默认值(o,false,null)与每个线程对其进行的操作同步;
  • 启动线程的操作与线程中的第一个操作同步;
  • 线程T2的最后的操作与线程T1发现线程T2已经结束同步;(isAlive,join可以判断线程是否终结)
  • 如果线程T1中断了T2,那么线程的T1中断操作与其他所有线程发现T2被中断同步;

       通过抛出InterruptedException异常,或者调用Thread.interrupted或者Thread.isInterrupted。

   同步:即可见,也就是不会被缓存。

八、Happens-before先行发生原则

       happens-before关系用于描述两个有冲突的动作之间的顺序,如果一个action happens-before

       另一个action,则第一个操作可被第二个操作可见;JVM需要实现如下happens-before规则:

  •  某个线程中的每个动作都 happens-before 该线程中该动作后面的动作;
  •  某个线程上的 unlock 动作都 happens-before 同一个线程上后续的 lock  动作;
  •  对某个 volatile 字段的写操作 happens-before 每个后续对 volatile 字段的读操作;
  •  在某个线程对象上调用 start() 方法 happens-before 被启动线程中的任意操作;
  •  如果在线程 t1 中调用了 t2.join() ,那么 t2 中的所有操作对 t1 可见;
  •  如果某个动作 a happens-before 动作 b,且 b happens-before 动作 c ,那么存在 a happens-before c;

九、final 在JVM 中的处理

     1)  如果 final 修饰了对象的字段,那么当线程看到该对象时,读取到的字段值一定是最新的;

     2)  通常被 static final 修饰的字段,不能被修改。然而 System.in 、System.out、System.err 被final 修饰,却

        可以被修改,这个是遗留问题,必须允许通过 set 方法改变,我们将这些字段称为写保护,以区别于普通

        的 final 字段。

十、Word Tearing 字节处理

       有些处理器(尤其是早期的Alphs处理器)没有提供写单个字节的功能。在这样的处理器上更新 byte 数组,若

       只是简单地读取整个内容,更新对应的字节,然后将整个内容写回内存,将是不合法的。

       这个问题有时候被称为 “字节分裂” ,更新单个字节有难度的处理器,就需要寻找其它方式解决问题,因此,

      编程时需要注意,尽量不要对 byte[] 中的元素进行重新赋值,更不要在多线程中这样做。

十一、double 和 long 的特殊处理

      —> 由于《Java语言规范》的原因,对非 volatile 的 double、long 的单次写操作是分两次进行的,每次操作其中的

      32位,这就可能导致第一次写入后,读取时,读取的是脏数据,等到第二次写完成之后,才能读取到正确值。

      —> 读写 volatile 修饰的 double、long 是原子性的。

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值