JUC并发编程——JMM详解(基于狂神说得到学习笔记)

news2024/11/16 4:42:51

JMM

什么是JMM (Java Memory Model)

参考文献JMM概述-CSDN博客

内存模型可以理解为在特定的操作协议下,对特定的内存或者高速缓存进行读写访问的过程抽象描述,不同架构下的物理机拥有不一样的内存模型,Java虚拟机是一个实现了跨平台的虚拟系统,因此它也有自己的内存模型,即Java内存模型(Java Memory Model, JMM)。

因此它不是对物理内存的规范,而是在虚拟机基础上进行的规范从而实现平台一致性,以达到Java程序能够“一次编写,到处运行”。

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。本地内存它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化之后的一个数据存放位置

JMM的三大特点

参考文献JMM概述-CSDN博客

原子性

一个操作不能被打断,要么全部执行完毕,要么不执行。如同事务一样

可见性

一个线程对共享变量做了修改之后,应该通知其他线程,使其他线程能够立即看到共享变量被修改

Java内存模型是通过将在工作内存中的变量修改后的值同步到主内存,在读取变量前从主内存刷新最新值到工作内存中,这种依赖主内存的方式来实现可见性的。

无论是普通变量还是volatile变量都是如此,区别在于:volatile的特殊规则保证了volatile变量值修改后的新值立刻同步到主内存,每次使用volatile变量前立即从主内存中刷新,因此volatile保证了多线程之间的操作变量的可见性,而普通变量则不能保证这一点。

除了volatile关键字能实现可见性之外,还有synchronized,Lock,final也是可以的。

使用synchronized关键字,在同步方法/同步块开始时(Monitor Enter),使用共享变量时会从主内存中刷新变量值到工作内存中(即从主内存中读取最新值到线程私有的工作内存中),在同步方法/同步块结束时(Monitor Exit),会将工作内存中的变量值同步到主内存中去(即将线程私有的工作内存中的值写入到主内存进行同步)。

使用Lock接口的最常用的实现ReentrantLock(重入锁)来实现可见性:当我们在方法的开始位置执行lock.lock()方法,这和synchronized开始位置(Monitor Enter)有相同的语义,即使用共享变量时会从主内存中刷新变量值到工作内存中(即从主内存中读取最新值到线程私有的工作内存中),在方法的最后finally块里执行lock.unlock()方法,和synchronized结束位置(Monitor Exit)有相同的语义,即会将工作内存中的变量值同步到主内存中去(即将线程私有的工作内存中的值写入到主内存进行同步)。

final关键字的可见性是指:被final修饰的变量,在构造函数数一旦初始化完成,并且在构造函数中并没有把“this”的引用传递出去(“this”引用逃逸是很危险的,其他的线程很可能通过该引用访问到只“初始化一半”的对象),那么其他线程就可以看到final变量的值。

有序性

在单线程的情况下,代码总是串行地从前往后执行,但在多线程并发情况下,代码执行的顺序可能就会出现乱序。虽然在线程与线程之间,所有操作都是无序的,但我们能够保证:在线程内部所有操作都是有序的,因此,我们在编程过程中可以适当忽略掉无序性。但我们能够使用Java中的一些特性,使线程与线程之间的操作变得有序,如synchronized、volatile、lock等

JMM原理

参考博客:从线程三大特性深入理解JMM(Java 内存模型) - 知乎 (zhihu.com)

JJM本质上是约定了线程之间或线程内部数据交换的规则,在JJM约定下,我们将内存空间分为两个部分,主内存本地内存注意:本地内存是一个抽象的概念,其实际并不存在,它包含了缓存,写缓冲区,寄存器以及其他硬件和编译器的优化

主内存:

JMM规定了所有的变量都存储在主内存中(此处的主内存与物理硬件的主内存名字一样,两者也可以“类比”,但物理上它仅是虚拟机内存的一部分)

本地内存(工作内存):

每条线程都有自己的工作内存(Working Memory,可以和处理器高速缓存“类比”),线程的工作内存中保存了该线程使用的变量的主内存副本。

内存数据的8种操作:

  • 主内存中的操作:

    • **lock(锁定):***把一个变量标记为一条线程的独占状态。
    • ***unlock(解锁):***把一个处于锁定状态的变量释放出来。
    • ***read(读取):***它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 *load* 动作使用。
    • ***write(写入):***它把 *store* 操作从工作内存中得到的变量值放入主内存的变量中。
  • 工作内存中的操作:

    • load(载入)😗 它把 *read* 操作从主内存中得到的值放入工作内存的变量副本中。
    • ***use(使用):***它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
    • ***assign(赋值):***它把一个从执行引擎收到的赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
    • ***store(存储):***它把工作内存中一个变量的值传送到主内存中,以便后续的 *write* 操作使用。

    在这里插入图片描述

JMM 还规定了上述 8 种基本操作,需要满足以下规则:

  1. 不允许 *read* 和 *load* 、*store* 和 *write* 操作单一出现。
  2. 不允许一个线程丢弃它最近的 *assign* 操作,及工作内存值被改变之后必须同步回主内存。
  3. 不允许一个线程无原因的(没有发生过任何 *assign* 操作)把数据从线程的工作内存同步回主内存
  4. 一个新变量只能从主存中”诞生“,也就是说 *user* 、*store* 操作之前,必须先执行 *assign* 和 *load* 操作。
  5. 一个变量在同时只能被一个线程 *lock* ,但可以被同一个线程多次 *lock* ,之后只有执行相同次数的 *unlock* 才能被解锁。
  6. 如果一个变量执行了 *lock* 操作,将会清空工作内存中的此变量的值,在执行引擎使用这个变量前,需要重新执行 *load* 或 *assign* 操作初始化变量值。
  7. 如果一个变量没有被 *lock* ,那就不允许对它执行 *unlock* 操作,也不允许去 *unlock* 一个被其他线程 *lock* 的变量。
  8. 对第一个变量执行 *unlock* 之前,必须把此变量同不回主内存中(执行 *store* 、 *write* 操作)。

--------------------------以上来自参考博客,讲的非常详细,同时因为最近读了图解操作系统,刚好读到内存管理部分,因此对这一块的理解非常快,如果读者认为这一块比较难懂,不妨回头先研究操作系统对于内存管理的部分,再回顾以下内存的基本原理,回头再看这一块,理解起来就会很快了-------------------------------

但是在普通编程中,我们可能会出现以下示例的情况

package Volatile;

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    private static int num = 0;
    public static void main(String[] args) throws InterruptedException {// 主线程

        new Thread(()->{// 线程1
            while(num == 0){

            }
        }).start();
        TimeUnit.SECONDS.sleep(1);

        num = 1;
        System.out.println(num);
    }
}
/**
 * 在本示例中,按照一贯的思想:
 * 线程一将先运行,主线程睡眠1秒
 * 当主线程睡眠时,因为num=0,因此线程一一直在跑
 * 当主线程醒来时,会在抢到CPU时将num变量修改为1,此时num=1
 * 主线程打印num变量,结果为1
 * 按道理来讲,num=1,当线程一应当停止while循环并结束线程
 * 当读者运行一下这段程序,会发现什么?
 * 答:程序还在跑,并没有停止,也就是说,线程一根本没有停止!
 */

通过学习上面的JMM,我们可以回答为什么这个程序一直在跑的原因:当主线程修改num变量时,并没有通知线程一,也就是说,线程一不知道num已经被修改了。

这样的编程没有遵循JMM的约定,即:可见性(主线程并没有通知线程一变量已被改变)

因此,在并发编程中:如若一个线程要修改共享资源,必须要通知其它线程。

下一章节我们将看到Java是如何解决这类问题的

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

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

相关文章

基于springboot的高校科研管理系统(源码+调试+LW)

项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据你想解决的问题,今天给…

JavaEE初阶学习:Servlet

1.Servlet 是什么 Servlet 是一种 Java 程序,用于在 Web 服务器上处理客户端请求和响应。Servlet 可以接收来自客户端(浏览器、移动应用等)的 HTTP 请求,并生成 HTML 页面或其他格式的数据,然后将响应发送回客户端。S…

AT6558--北斗定位芯片 一款高性能 BDS/GNSS 多模卫星导航接收机 SOC 单芯片

一、AT6558的由来: AT6558 是一款高性能 BDS/GNSS 多模卫星导航接收机 SOC 单芯片,采用 55nm CMOS工艺,片上集成射频前端,数字基带处理器,32位的 RISC CPU,电源管理功能。AT6558可以达到同类产品的顶级性能…

微信小程序怎么实现扫码一键连WiFi功能

微信小程序如何实现扫码一键连接WiFi功能 一、引言 在互联网时代,WiFi已经成为了人们生活中不可或缺的一部分。在公共场所或者朋友家,我们经常需要连接WiFi以获得更高速的网络体验。然而,传统的方式需要输入冗长的密码,十分麻烦…

[开源]MIT开源协议,基于Vue3.x可视化拖拽编辑,页面生成工具

一、开源项目简介 AS-Editor 基于 Vue3.x 可视化拖拽编辑,页面生成工具。提升前端开发效率,可集成至移动端项目作为通过定义 JSON 直接生成 UI 界面。 二、开源协议 使用MIT开源协议 三、界面展示 四、功能概述 基于Vue可视化拖拽编辑,…

AI系统ChatGPT源码+详细搭建部署教程+支持GPT4.0+支持ai绘画(Midjourney)/支持OpenAI GPT全模型+国内AI全模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统AI绘画系统,支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署…

UE蓝图中Map的遍历

一、让Map节点连接一个Keys节点,这个Keys节点的功能是将Map的Kay值收集成一个数组。 二、让Keys节点连接Foreach节点来遍历Keys返回的数组。 三、使用Find节点在Map中查找Keys提供的Key值。 局部就是这样的:

centos7 部署oracle完整教程(命令行)

centos7 部署oracle完整教程(命令行) 一. centos7安装oracle1.查看Swap分区空间(不能小于2G)2.修改CentOS系统标识 (由于Oracle默认不支持CentOS)2.1.删除CentOS Linux release 7.9.2009 (Core)(快捷键dd)&…

【Django 02】数据表构建、数据迁移与管理

1. Django 构建数据表创建与数据迁移 1.1 数据表创建 1.1.1 模块功能 如前所述,models.py文件主要用一个 Python 类来描述数据表。运用这个类,可以通过简单的 Python 代码来创建、检索、更新、删除 数据库中的记录而无需写一条又一条的SQL语句。今天的例子就是在…

基于YOLOv8的多目标检测与自动标注软件【python源码+PyqtUI界面+exe文件】【深度学习】

基本功能演示 摘要:YOLOv8是YOLO系列最新的版本,支持多种视觉任务。本文基于YOLOv8的基础模型实现了80种类别的目标检测,可以对图片进行批量自动标注,并将检测结果保存为YOLO格式便于后续进行其他任务训练。本文给出完整的Python实…

PW2162芯片

可知该芯片为4.5V~16V输入,2A工作电流 ,下图为官方参考原理图。 输出的电压根据下公式计算,可知改变选用不同阻值的R1和R2,可控制输出不同的电压。 原理图上的电容主要用于滤波,电感是为了防止电流过大。 立创EDA画板…

【Qt控件之QCommandLinkButton】概述及使用

概述 QCommandLinkButton小部件提供了一个Vista风格的命令链接按钮。 命令链接是Windows Vista引入的一种新控件。它的使用方式类似于单选按钮,用于在一组互斥选项之间进行选择。命令链接按钮不应单独使用,而是作为向导和对话框中单选按钮的替代品&…

移动App安全检测的必要性,app安全测试报告的编写注意事项

随着移动互联网的迅猛发展,移动App已经成为人们日常生活中不可或缺的一部分。然而,虽然App给我们带来了便利和乐趣,但也伴随着一些潜在的安全风险。黑客、病毒、恶意软件等威胁着用户的隐私和财产安全,因此进行安全检测就显得尤为…

YOLOv7改进:动态蛇形卷积(Dynamic Snake Convolution),增强细微特征对小目标友好,实现涨点 | ICCV2023

💡💡💡本文独家改进:动态蛇形卷积(Dynamic Snake Convolution),增强细长微弱的局部结构特征与复杂多变的全局形态特征,对小目标检测很适用 Dynamic Snake Convolution | 亲测在多个数据集能够实现大幅涨点 收录: YOLOv7高阶自研专栏介绍: http://t.csdnimg.…

创新与合规共舞 百望云铸就未来档案数字化管理之路

随着人工智能技术与数字经济的深入发展,数据要素的基础性、战略性资源地位日益凸显,也驱动业内专家学者们持续深入思考——档案资源如何纳入数据资产框架内,如何面向数字中国、数字经济等国家战略发挥其凭证、情报、记忆、数据要素价值&#…

DPDK之eventdev_pipeline源码解析

DPDK之eventdev_pipeline源码解析 引言1 实现原理1.1 数据接收1.2 数据发送1.3 事件调度1.4 struct rte_event 2 核心API3 源码解析3.1 generic实现3.2 tx enq实现 引言 DPDK Eventdev库是DPDK基于事件驱动的编程模型。其中eventdev_pipeline实现了对该模型的应用例子。 1 实…

linux内核模块符号导出

一、内核模块符号导出简介 驱动程序编译生成的 ko 文件是相互独立的,即模块之间变量或者函数在正常情况下无法进行互相访问。而一些复杂的驱动模块需要分层进行设计,这时候就需要用到内核模块符号导出。   内核符号导出指的是在内核模块中导出相应的函…

[MAUI]深入了解.NET MAUI Blazor与Vue的混合开发

文章目录 Vue在混合开发中的特点创建MAUI项目创建Vue应用使用element-ui组件库JavaScript和原生代码的交互传递根组件参数从设备调用Javascript代码从Vue页面调用原生代码 读取设备信息项目地址 .NET MAUI结合Vue的混合开发可以使用更加熟悉的Vue的语法代替Blazor语法&#xff…

Cypress安装使用

node.js 安装使用Cypress总是会看见node.js,那就先看看node.js是什么。JavaScript以前运行需要在浏览器中(浏览器内置解释器),通过node.js框架内置v8引擎(也就是可以执行js脚本所需的工具),这样…

金翅擘海|人大女王金融硕士庞雪雨:行学之道,在自律、在勤勉、在止于至善

庞雪雨 中国人民大学-加拿大女王大学金融硕士2022-2023级行业高管班 光大保德信资产管理有限公司董事总经理 当我进入到人大校园的那一刻,映入眼帘的是明德楼,由此我想到了《大学》,大学开篇中讲到,大学之道,在明明德&…