Java内存模型—工作流程、volatile原理

news2024/11/25 3:00:51

导入

最近在做项目的时候发现很多业务上用到了多线程,通过多线程去提升程序的一个运行效率,借此机会来复盘一下关于并发编程的相关内容。为什么要使用volatile?volatile底层原理是什么?JMM内存模型解决的是什么问题?带着这些问题来分享分享我的成果。

正文

JMM内存模型是什么?

根据百度百科介绍:

Java Memory Model,java内存模型,描述了程序中各个共享变量(成员变量、静态变量、数据元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节。

注意:局部变量不存在线程之间共享,它属于方法内定义的参数,不受内存模型影响

为什么要有JMM内存模型?要解决什么问题?

在多线程通信的情况下,如何保持读取一致是重中之重,解决存储在主内存的数据和CPU中工作内存的数据不一致问题,解决编译器对代码进行指令重排序导致执行数据不一致的问题,这些都是JMM去帮助我们去完成的。

那JMM具体是怎么工作的呢?且看我接下来的分享

JMM工作流程—抽象

下面是JMM的抽象结构示意图:

JMM去决定了一个线程对共享变量的写入何时对另一个线程可见。线程之间的共享变量都存在主内存中,而每一个线程包含了一个工作内存(本地),在本地内存中存储了共享变量的副本。线程之间工作内存中的变量是不能相互访问的,必须通过主内存获取

抽象工作流程如下:

  1. 若线程1修改了本地内存中的共享变量,将共享变量最新结果刷新到主内存中
  2. 线程2到主内存中读取线程1修改之后共享变量

实战演练—加锁+volatile前

我们结合程序来研究研究:

自定义线程类

class MyThread extends Thread {
    private boolean flag = false;

    public boolean isFlag() {
        return flag;
    }
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("flag=" + flag);
    }
}

main函数

public class SolveVolatile {
    public static void main(String[] args) {
        MyThread a = new MyThread();
        a.start();
        for (; ; ) {
            if (a.isFlag()) {
                System.out.println("进来了吗?");
            }
        }
    }

}

运行程序:

发现,一直都不输出“进来了吗?”并且程序一直是处于运行状态的,结合上面讲到的JMM模型,其实是数据可见性问题


JMM工作流程—具体

上面讲到了JMM工作流程,我们来结合这个程序具体来看看它是怎么工作的!

  1. read(读取):从主内存读取数据
  2. load(载入):将主内存读取到的数据写入工作内存
  3. use(使用)从工作内存读取数据来计算
  4. assign(赋值):将计算好的值重新赋值到工作内存中
  5. store(存储):将工作内存数据写入主内存
  6. write(写入:将store过去的变量值赋值给主内存中的变量

我们会发现线程2对flag变量的值修改了之后线程1其实是并不知道的,导致程序一直都不会输出“进来了吗?”这句话,线程1 的工作内存中其实还一直保存着共享变量原来的值。

那如何解决这个问题呢?给变量添加volatile关键字修饰、同步代码块加锁


volatile修饰共享变量、加锁

线程类

class MyThread extends Thread {
    private volatile boolean flag = false;

    public boolean isFlag() {
        return flag;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("flag=" + flag);
    }
}

Main函数

public class SolveVolatile {
    public static void main(String[] args) {
        MyThread a = new MyThread();
        a.start();
        for (; ; ) {
            synchronized (a) {
                if (a.isFlag()) {
                    System.out.println("进来了吗?");
                }
            }
        }
    }

}

此时输出结果为:


为什么加锁和volatile可以解决数据可见性问题?

此时JMM内部工作结构就变成了这样:

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

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

当某个线程进入到sysnchronized代码块,线程获得锁之后会清空本地的工作内存,重新从主内存中读取共享变量的副本到工作内存中,此时线程在执行代码判断的时候发现共享变量值被修改了。

volatile的底层原理是什么?

结合前面讲到的JMM工作流程,当线程对共享变量的副本数据进行了修改之后,会立马写回到主内存中,此时各个线程中工作内存的共享变量副本就失效了,需要重新去主内存中读取

那其他线程怎么知道某个线程修改了共享变量呢?我们可不可以设置一个监听的人,只要有线程改变了值我就去主内存中读取?那就要讲讲MESI了!


MESI(缓存一致性协议)—硬件方式

 

数据都是通过总线以流的形式传输,线程2将flag值改变之后,下一步应该是写入主存中,会经过总线,这时候线程1通过总线嗅探机制监听到flag值的改变,线程1去主存中读取flag的值,读到的值还是false,此时线程2还没有回写到主存中,此时就产生了偏差所以在数据要往主存中回写的时候store之前就加上锁,在主内存中write回写完了再释放锁。

CPU通过总线嗅探机制可以感知到数据变化从而自己缓存里的数据失效重新读取


总结

通过加锁和volatile我们可以解决多核cpu并发线程出现数据不一致、可见性问题,正式因为线程之间通信对我们完全的透明,所以在项目中会出现内存可见性的问题,追根溯源去了解原理,在开发过程中除了知道怎么用,还能知道为什么这么用!

如果有想要交流的内容欢迎在评论区进行留言,如果这篇文档受到了您的喜欢那就留下你点赞+收藏+评论脚印支持一下博主~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/723051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于 OpenCV 开发实现眨眼计数器

文末提供免费的源代码下载链接 在本教程中,我们将了解如何使用 Dlib、Python 和 OpenCV 创建眨眼检测器/计数器。 我们将首先使用 Dlib 的人脸检测器来检测视频中的人脸。然后我们将使用 Dlib 中的形状预测器来确定眼睛在面部的位置。 最后,我们将使用眼睛的标志来计算眼睛…

小白必看:这些项目可以让你轻松月入过万

月入五W的互联网副业野路子。总有一个适合你!刷到这篇回答的。就不要在看其他的了。 这些互联网项目你也能做 1、认识个哥们,每天去一些热门的寺庙拍视频开直播,帮人代请手串,每串赚15元,月入5万多,非常非常稳定。 2…

基于SpringBoot+vue的口腔管家平台设计与实现

博主介绍: 大家好,我是一名在Java圈混迹十余年的程序员,精通Java编程语言,同时也熟练掌握微信小程序、Python和Android等技术,能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

Image Sensor的像素时钟计算

本文介绍Image Sensor的像素时钟计算。 在配置Image Sensor寄存器的过程中,像素时钟至关重要,它的正确设置关系到帧率,曝光参数的正确与否。我们从相关资料中了解到像素时钟有多种方法,究竟该选择哪一种呢,本文来对常…

【C++】函数绑定器技术

1. 函数绑定器技术,什么情况可以用到这种技术实现? 占位符和函数绑定器技术(如std::bind)可以在以下情况下使用: 参数绑定:当你想要绑定函数的某些参数,但又不想立即提供这些参数的值时,可以…

在idea中使用Git技术

1.配置git环境 打开idea,点击file->setting->搜索git, 将git的安装路径填写进去 2.去gitee创建一个远程仓库 3.拉入一个.gitignore文件,过滤掉不需要管理的文件 4.在idea进行如下操作 5.选择要提交的内容 目前只是保存在了本地仓库 6.推送到远端…

简单尝试将go项目用宝塔部署到服务器

最近写了一个go项目的小demo打算放到服务器上,但是中间遇到一些问题,这里我记录下: 一、找一个文件夹将打包后的go项目放进去,如图 二、安装执行exe文件的wine 终端执行命令 yum install wine 终端在这,如图&#xf…

macOS Sonoma 14 beta 3 (23A5286g) Boot ISO 原版可引导镜像下载

macOS Sonoma 14 beta 3 (23A5286g) Boot ISO 原版可引导镜像,7 月 5 日(北京时间今日凌晨)已发布 本站下载的 macOS 软件包,既可以拖拽到 Applications(应用程序)下直接安装,也可以制作启动 U…

从0开始学架构-架构的定义

从0开始学架构-架构的定义 文章目录 从0开始学架构-架构的定义系统与子系统模块与组件框架与架构软件框架(Software framework)软件架构区别 重新定义架构:4R 架构软件架构重新定义 三组容易混淆的概念: 系统与子系统模块与组件框…

文档翻译器那么多,你知道文档翻译pdf哪个软件好吗?

曾经有一个年轻的学生,名叫小杰。他是一名热爱学习的人,对于跨文化交流和学习外语充满了好奇和热情。然而,他在阅读一些重要的学术论文和研究资料时,常常遇到一个令人头痛的问题:如何将外文的pdf文档准确地翻译成自己的…

小白必看!关于欧盟商标注册须知事项?

一、什么是欧盟商标? 答: 欧盟商标是指根据CTMR(欧共体商标条例)规定的条件获得OHIM(欧共体内部市场协调局)注册的,在欧盟范围内有效的,用来识别和区分商品或服务的标记。 二、欧盟…

【前端面试专栏】<script> 脚本以及 <link> 标签对 DOM 的影响

🐱 个人主页:不叫猫先生,公众号:前端舵手 🙋‍♂️ 作者简介:2022年度博客之星前端领域TOP 2,前端领域优质作者、阿里云专家博主,专注于前端各领域技术,共同学习共同进步…

解决方法:python: not found问题

一、问题 测试环境:Ubuntu22.04.1 在运行一些脚本文件的时候,报以下错误: ./build.sh: python: not found二、原因 python 可能被 python2 或者 python 3 代替了导致映射不到。 三、解决方法 查看所有的 python 映射 ls -l /usr/bin/py…

现代化个人博客系统 ModStartBlog v7.7.0 博客关键词优化,附件上传重构

现代化个人博客系统 ModStartBlog v7.7.0 博客关键词优化,附件上传重构 ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用,支持后台一键快速安装,让开发者能快的实现业务功能开发。 系统完全开源,基…

【电路原理学习笔记】第2章:电压、电流和电阻:2.5 电阻

第2章:电压、电流和电阻 2.5 电阻 当固体导体中有电流时,自由电子在该材料中运动,有时会与原子发生碰撞。这些碰撞使电子失去一些能量,因此它们的运动会受到限制。碰撞越多,电子的流动就越受限制。这一限制因材料类型…

基于Java+Swing+mysql实现垃圾分类管理系统

基于JavaSwingmysql实现垃圾分类管理系统 一、系统介绍二、功能展示1.登陆2.社区管理3.设备管理4.垃圾管理 三、其它1.其他系统实现2.获取源码 一、系统介绍 该系统实现了 管理员:系统登陆、社区管理、设备管理、垃圾管理 小区负责人:查看垃圾分类信息、垃圾站信息…

【雕爷学编程】Arduino动手做(136)---0.91寸OLED液晶屏模块2

0.91寸OLED液晶屏显示模块参数 驱动芯片:SSD1306 支持接口:I2C 显示颜色:白色 高分辨率: 12832 可视角度:大于160 工作电压:3.3V / 5V 模块大小:36 x 12.5(mm) 项目之三&…

2022年4月自写json转table记录

表头属性 属性名描述类型默认值key 设置字段名。通常是表格数据列的唯一标识 string-label 设置列的标题。 string-width 设置列宽。若不填写,则自动分配;若填写,则支持值为:数字、百分比。如: width: 200 / width: 30…

mysql的两种安装方式(yum在线安装和通用二进制)

文章目录 msqly的安装一、yum在线安装二、通用二进制安装mysql msqly的安装 一、yum在线安装 yum是一种在线安装方式,通过官网网址在linux下载安装 首先是配置一个yum安装源 yum install http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm也…

基于STM32单片机的智能家居烟雾温度火灾防盗报警的设计与实现

功能介绍 以STM32单片机作为主控系统;LCD1602液晶显示屏来显示显示测得的值;SR501人体红外感应是否有人进行防盗;通过烟雾传感器MQ-2获取前的烟雾值;通过DHT11温湿度传感器来获取当前的温湿度;所有的信息通过通过esp82…