Code Ease Code Ease
  • 个人博客网站 (opens new window)
  • 好用的工具网站 (opens new window)
  • Java核心基础
  • 框架的艺术
  • 分布式与微服务
  • 开发经验大全
  • 设计模式
  • 版本新特性
数据库系列
大数据+AI
  • xxl-job
运维与Linux
  • 基于SpringBoot和BootStrap的论坛网址
  • 基于VuePress的个人博客网站
  • 基于SpringBoot开发的小功能
  • 做一个自己的IDEA插件
程序人生
关于我
  • 分类
  • 标签
  • 归档

神秘的鱼仔

你会累是因为你在走上坡路
  • 个人博客网站 (opens new window)
  • 好用的工具网站 (opens new window)
  • Java核心基础
  • 框架的艺术
  • 分布式与微服务
  • 开发经验大全
  • 设计模式
  • 版本新特性
数据库系列
大数据+AI
  • xxl-job
运维与Linux
  • 基于SpringBoot和BootStrap的论坛网址
  • 基于VuePress的个人博客网站
  • 基于SpringBoot开发的小功能
  • 做一个自己的IDEA插件
程序人生
关于我
  • 分类
  • 标签
  • 归档
服务器
  • Java核心基础

    • 基础篇

    • 集合类

    • JVM虚拟机

    • Java并发

      • 产品经理问我:手动创建线程不香吗,为什么非要用线程池呢?
      • 面试官:我问的是Java内存模型,你回答堆栈方法区干嘛?
        • (一)概述
        • (二)Java内存模型
        • (三)JMM数据的原子操作
        • (四)JMM缓存不一致问题
        • (五)总结
      • 指令重排序、内存屏障很难?看完这篇你就懂了!
      • Volatile只会用不知道原理?一篇文章带你深究volatile
      • 有关synchronized锁的知识点,我用一篇文章总结了
      • 面试被问AQS、ReentrantLock答不出来?这些知识点让我和面试官聊了半小时!
      • 大厂面试题:你知道JUC中的Semaphore、CyclicBarrier、CountDownLatch吗
      • 关于ThreadLocal的九个知识点,看完别再说不懂了!
  • 框架的艺术

  • 分布式与微服务

  • 开发经验大全

  • 版本新特性

  • Java
  • Java核心基础
  • Java并发
CodeEase
2023-09-19
目录

面试官:我问的是Java内存模型,你回答堆栈方法区干嘛?

作者:鱼仔
博客首页: codeease.top (opens new window)
公众号:Java鱼仔

# (一)概述

很多人会把Java内存区域(运行时数据区)和Java内存模型(JMM)搞混,这两者是完全不一样的东西。

Java内存区域是指JVM运行时数据分区域存储,而Java内存模型是定义了线程和主内存之间的抽象关系,了解Java内存模型是学好Java并发编程的基础。

# (二)Java内存模型

Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。我们来看一张图:

2-1.jpeg

每个线程拥有一个自己的私有工作内存,需要变量时从主内存中拷贝一份到工作内存,如果更新过变量之后再将共享变量刷新到主内存。

但是两个线程之间,是没有办法读取对方工作内存中的变量值的。看一个例子:

public class Test {
    private static boolean flag=false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("waiting");
                while (!flag){}
                System.out.println("in");
            }
        }).start();

        Thread.sleep(2000);
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("change flag");
                flag=true;
                System.out.println("change success");
            }
        }).start();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

首先定义了一个静态变量flag为false,A线程等待flag等于true后输出in,于是我们新开一个线程将flag修改为true。结果是A线程依旧无法输出in。

2-2.png

原理看Java内存模型的图就理解了,不同的线程修改变量,对本地线程是不可见的。

# (三)JMM数据的原子操作

通过上面这段代码,我们已经知道了Java内存模型的结构,那么工作内存和主内存之间是如何读取变量又是如何修改变量的呢?JMM提供了对变量的一系列原子操作。我们先不讲理论,看个图,这个图描述了上面一段代码的执行过程:

2-3.jpeg

整个过程一共十步,重复几个步骤不讲了,我把不重复的六个操作列一下:

read:从主内存读取数据

load:将主内存读取到的数据写入工作内存

use :从工作内存中读取数据来使用

assign:把计算好的值重新赋值到工作内存中

store:将工作内存数据写入主内存

write:将store过去的变量赋值给主内存中的变量

通过上面的图,对下面六个原子操作的理解应该可以更加深刻了。JMM的原子操作一共有八个,下面列出剩下的两个

lock:将主内存变量加锁,标识为线程独占状态

unlock:将主内存变量解锁,解锁后其他线程可以锁定该变量

# (四)JMM缓存不一致问题

从前面的例子我们已经看到了,一个线程修改完数据,另外一个线程无法立即可见,这就是JMM缓存不一致的问题,有两种解决办法:

加锁:

还记得我们没有用到过的JMM原操作的最后两个吗,lock和unlock,使用这两个操作就可以实现缓存一致性,一个线程想要获取某个主内存变量时,先使用lock将主内存变量加锁,只有他才能使用,等用完后再unlock,其他线程才能竞争。但是加锁意味着性能低。

MESI缓存一致性协议:

这个协议涉及到cpu的总线嗅探机制,从上面的JMM执行的流程图中我们可以看到当某个线程修改了共享变量后,他会回写到主内存,MESI缓存一致性协议就是通过cpu的总线嗅探机制,将其他也正在使用该变量的线程的数据失效掉,使得这些线程要重新读取主内存中的值,从而保证缓存最终一致性。(volatile的实现原理)

# (五)总结

Java内存模型在多并发中十分重要,包括后面学习volatile或者synchronized这个关键字的时候,都会回到这个Java内存模型中。

上次更新: 2025/04/29, 17:22:06
产品经理问我:手动创建线程不香吗,为什么非要用线程池呢?
指令重排序、内存屏障很难?看完这篇你就懂了!

← 产品经理问我:手动创建线程不香吗,为什么非要用线程池呢? 指令重排序、内存屏障很难?看完这篇你就懂了!→

最近更新
01
AI大模型部署指南
02-18
02
半个月了,DeepSeek为什么还是服务不可用
02-13
03
Python3.9及3.10安装文档
01-23
更多文章>
Theme by Vdoing | Copyright © 2023-2025 备案图标 浙公网安备33021202002405 | 浙ICP备2023040452号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式