JMM内存模型和volatile

JMM内存模型

JMM内存模型是java内存模型,又叫java线程内存模型。

首先理解一下并发和并行的概念。

​ 并发:是一种逻辑架构,指的是在同一时刻只能由一条指令执行,在宏观意义上看起来是多个进程或者线程同时执行,但是在微观意义上,只是把时间分成若干段,多个进程之间快速交替的执行。可以在单核或者多核上执行。

​ 并行:在同一时刻,有多条指令在多个处理器上同时执行,无论是在宏观或者微观来看,二者都是在一块执行的。只会在多核处理器上发生。

JMM是一种逻辑架构,每个线程都有自己的本地方法栈,本地方法栈中局部变量表存放的是本地变量 和 共享变量的副本。线程要读取共享变量的数据,首先要从主内存中拷贝一份到本地内存,然后再去使用。

img

下面通过一个案例来分析JMM内存工作模式

 private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        //线程A
        new Thread(()->{
            System.out.println("A线程执行");
            int i = 0;
            while (flag){
                i++;
            }
            System.out.println("A线程执行完毕,i的值为"+i);
        }).start();

        Thread.sleep(100);
        //线程B
        new Thread(()->{
            flag = false;
            System.out.println("B线程修改flag值为false");
        }).start();
    }

​ 线程A读取共享变量flag循环增加i的值,当跳出循环时,打印i的值。线程B等待100毫秒后启动,改变flag为flase。正常情况下,当flag是false时,线程A中的循环语句就会跳出,但是在控制台我们发现程序一直在运行,线程A并没有停止,那就说明线程A读取的flag是true

在这里插入图片描述

第一步首先在主内存中拷贝flag=true到本地内存A,执行i++操作,然后线程B在主内存中拷贝flag=true到本地内存B,线程B修改flag=flase,立刻会刷新到本地内存,但是要过一段时间才会刷新到主内存,而此时线程A的本地内存的共享变量为失效时,才会读取主内存的变量。所以程序这样的情况时因为线程A的本地内存一直在使用副本变量,并没有去主存中做更新。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-01BH5Pb6-1603358857121)(C:%5CUsers%5CA%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20200917145946406.png)]

volatile

volatile关键字保证了变量的可见性和有序性,但不能保证原子性。

可见性

先来解释一下volatile为什么会保证可见性(可见性就是指当一个线程修改了变量之后,另一个线程会立马看到修改的值),还是使用上一个例子,这次把flag变量加是volatile,再来看一下运行结果,可以看到结果是线程A跳出了循环,打印i的值。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Rm5xQ1K-1603358857123)(C:%5CUsers%5CA%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20201010125506324.png)]是

使用的是汇编指令lock,通过缓存一致性协议(MESI),锁缓存行来实现可见性。

为什么不能保证原子性,所谓的原子性指的是一组指令要么全部成功,要么全部失败。拿i++举例,该操作分为三步:

1,线程读取i的值

2,i进行自增计算

3,刷新回i的值。

volatile int i = 1,两个线程同时执行i++操作,即使加了volatile关键字,当A和B线程同时执行完1,2步骤时,此时在内存中的i还没有发生变化,假设A线程先执行了第三步,此时触发缓存一致性协议,线程B立即修改i的值为2,但是线程B已经执行过了i++操作,只不过还没有执行第三步,现在再执行第三步,i依旧等于2。所以volatile 并不能保证原子性。

有序性

什么是有序性,我们的程序中写的代码CPU中可能是乱序执行的,就是所谓的指令重排,这样会提交执行的效率。但是有一个规范,就是在单线程的情况下是不能影响最终执行的结果(as-if-serial语义)。但是在多线程的情况下可能会出现问题,有序性就是确保程序指令顺序执行。

volatile禁止指令重排,通过内存屏障来完成。内存屏障类似AOP的概念,在指令前后加入一些指令禁止重排序。

happens-before

因为指令重排会影响到多线程之间的安全问题,需要happens-before原则来规定一些禁止编译优化的场景,保证并发编程的安全。

happens-before8大规则

1.程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;

2.锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;

3.volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;

4.传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;

5.线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;

6.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;

7.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;

7.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;

8.对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值