黑马程序员jvm笔记(六)--JMM内存模型部分心得

JMM内存模型

1.计算机结构

QQ截图20220109114051

输入设备:就是我们的鼠标,键盘

存储器:对应的就是我们的内存,缓存

运算器和控制器共同组成了cpu

而输出设备就比如显示屏,打印机。

我们重点来聊一下缓存:

2.缓存

其实,当我们说计算机运行效率低下,速度慢,往往不是cpu的锅。而问题所在一般都是内存访问速度太慢。

CPU的运算速度和内存的访问速度相差比较大。这就导致CPU每次操作内存都要耗费很多等待时间。内存的读写速度成为了计算机运行的瓶颈。

于是就有了在CPU和主内存之间增加缓存的设计。最靠近CPU的缓存称为L1,然后依次是L2,L3和主内存。

CPU缓存模型如图下图所示。

QQ截图20220109114428

运行速度: L1cache >L2cache >L3cache >内存

所以,系统会先访问L1缓存>L2缓存>L3缓存>内存

具体的速度如下:

QQ截图20220109114528

3.java内存模型概念

Java内存模型,是Java虚拟机规范中所定义的一种内存模型,Java内存模型是标准化的,屏蔽掉了底层不同计算机的区别。

Java内存模型是一套规范,描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

具体如下:

QQ截图20220109114829

Java内存模型如上面所示:规定了工作内存和主内存的概念及其交互。

对共享数据的可见性、有序性、和原子性的规则和保障。

QQ截图20220109114927

工作内存和主内存可能在很多地方(cpu寄存器 缓存 或者主内存)

4.主内存和工作内存的交互

Java内存模型中定义了以下8种操作来完成,主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,虚拟机实现时必须保证下面提及的每一种操作都是原子的、不可再分的。

具体操作如下图:

QQ截图20220109115543

5.对主内存操作的三大问题

原子性问题描述

QQ截图20220220171059

解决原子性问题

上锁!

QQ截图20220220171237

可见性问题描述

可见性问题指的是,因为JMM内存模型规范,线程访问主内存的数据后会将数据复制到一个自己的共享内存里,若此时将主内存里的数据更改,就会引起数据不一致的可见性问题。

QQ截图20220220171525

可见性问题的解决方法

用volatile关键字

static volatile boolean run = true; 
public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{ 
        while(run){ // .... 
        
        } });
            t.start(); 
    Thread.sleep(1000); 
    run = false; // 线程t不会如预想的停下来 
}

QQ截图20220220171701

指的注意的是synchronized也可以保证代码内变量的可见性

static  boolean run = true; 
public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{ 
        while(run){ // .... 
        System.out.printIn();
        } });
            t.start(); 
    Thread.sleep(1000); 
    run = false; // 线程t不会如预想的停下来 
}

代码也会停下来

但性能更低。。。

有序性问题描述

我们来看如下代码:

int num = 0;
boolean ready = false; 
// 线程1 执行此方法 
public void actor1(I_Result r) {
    if(ready) { 
        r.r1 = num + num;
    }else {
        r.r1 = 1;
    } }
// 线程2 执行此方法 
public void actor2(I_Result r) {
    num = 2;
    ready = true;
}

它有集中结果呢?

  • 情况1:线程1 先执行,这时 ready = false,所以进入 else 分支结果为 1
  • 情况2:线程2 先执行 num = 2,但没来得及执行 ready = true,线程1 执行,还是进入 else 分支,结果为1
  • 情况3:线程2 执行到 ready = true,线程1 执行,这回进入 if 分支,结果为 4(因为 num 已经执行过了)

这些都是基于我们上面学的分析出来的结果,但实际上结果还有一个 0

线程2 执行 ready = true,切换到线程1,进入 if 分支,相加为 0,再切回线程2 执行 num = 2

出现这种结果,说明发生了指令重排,引起了有序性问题。

有序性问题解决办法

QQ截图20220220172545

有序性的理解

QQ截图20220220172653

有序性引起的double-check问题

QQ截图20220220172828

理解下就是:

QQ截图20220220145712

这是优点:double-checke机制可以减少重复创建对象的现象,提升效率。

但是忽略了有序性问题

我们创建对象时的字节码如下:

0: new #2 // class cn/itcast/jvm/t4/Singleton  分配空间
3: dup    //将引用地址放进操作数栈
4: invokespecial #3 // Method "<init>":()V  将对象完善
7: putstatic #4 // Field INSTANCE:Lcn/itcast/jvm/t4/Singleton; 将完善后的对象放进局部变量表

此时容易发生 4 ,7之间的指令重排,使得对象没完善之前就赋值给了INSTANCE对象,如果对象本来就很复杂,后面的线程就容易得到不完善的对象。

解决办法:

public final class Singleton {
    private Singleton() { }
    private static volatile Singleton INSTANCE = null; 
    public static Singleton getInstance() {
        // 实例没创建,才会进入内部的 synchronized代码块 
        if (INSTANCE == null) { 
            synchronized (Singleton.class) { 
                // 也许有其它线程已经创建实例,所以再判断一次 
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                } } }
        return INSTANCE;
    } }
happens-before

QQ截图20220220173643

CAS 与 原子类

1.CAS

QQ截图20220220173850

乐观锁的效率高于没优化之前的synchronized的效率,原理是不断比较旧值,确保旧值没被更改后赋值。

  • CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。

  • synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。

2.原子操作类

QQ截图20220220174149

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值