看完这篇,还不懂JAVA内存模型(JMM)算我输

news2024/9/24 3:27:33

前言

开篇一个例子,我看看都有谁会?如果不会的,或者不知道原理的,还是老老实实看完这篇文章吧。

@Slf4j(topic = "c.VolatileTest")
public class VolatileTest {
    
    static boolean run = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (run) {
                // do other things
            }
            
            // ?????? 这行会打印吗?
            log.info("done .....");
        });
        t.start();
        
        Thread.sleep(1000);

       // 设置run = false
        run = false;
    }
}
复制代码

main函数中新开个线程根据标位run循环,主线程中sleep一秒,然后设置run=false,大家认为会打印"done ......."吗?

答案就是不会打印,为什么呢?

JAVA并发三大特性

我们先来解释下上面问题的原因,如下图所示,

现代的CPU架构基本有多级缓存机制,t线程会将run加载到高速缓存中,然后主线程修改了主内存的值为false,导致缓存不一致,但是t线程依然是从工作内存中的高速缓存读取run的值,最终无法跳出循环。

可见性

正如上面的例子,由于不做任何处理,一个线程能否立刻看到另外一个线程修改的共享变量值,我们称为"可见性"。

如果在并发程序中,不做任何处理,那么就会带来可见性问题,具体如何处理,见后文。

有序性

有序性是指程序按照代码的先后顺序执行。但是编译器或者处理器出于性能原因,改变程序语句的先后顺序,比如代码顺序"a=1; b=2;",但是指令重排序后,有可能会变成"b=2;a=1", 那么这样在并发情况下,会有问题吗?

在单线程情况下,指令重排序不会有任何影响。但是在并发情况下,可能会导致一些意想不到的bug。比如下面的例子:

public class Singleton {
  static Singleton instance;
    
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}
复制代码

假设有两个线程 A、B 同时调用 getInstance() 方法,正常情况下,他们都可以拿到instance实例。

但往往bug就在一些极端的异常情况,比如new Singleton() 这个操作,实际会有下面3个步骤:

  1. 分配一块内存 M;

  2. 在内存 M 上初始化 Singleton 对象;

  3. 然后 M 的地址赋值给 instance 变量。

现在发生指令重排序,顺序变为下面的方式:

  1. 分配一块内存 M;

  2. 将 M 的地址赋值给 instance 变量;

  3. 最后在内存 M 上初始化 Singleton 对象。

优化后会导致什么问题呢?我们假设线程 A 先执行 getInstance() 方法,当执行完指令 2 时恰好发生了线程切换,切换到了线程 B 上;如果此时线程 B 也执行 getInstance() 方法,那么线程 B 在执行第一个判断时会发现 instance != null ,所以直接返回 instance,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常。

这就是并发情况下,有序性带来的一个问题,这种情况又该如何处理呢?

当然,指令重排序并不会瞎排序,处理器在进行重排序时,必须要考虑指令之间的数据依赖性。

原子性

如上图所示,在多线程的情况下,CPU资源会在不同的线程间切换。那么这样也会导致意向不到的问题。

比如你认为的一行代码:count += 1,实际上涉及了多条CPU指令:

  • 指令 1:首先,需要把变量 count 从内存加载到 CPU 的寄存器;
  • 指令 2:之后,在寄存器中执行 +1 操作;
  • 指令 3:最后,将结果写入内存(缓存机制导致可能写入的是 CPU 缓存而不是内存)。

操作系统做任务切换,可以发生在任何一条CPU 指令执行完。假设 count=0,如果线程 A 在指令 1 执行完后做线程切换,线程 A 和线程 B 按照下图的序列执行,那么我们会发现两个线程都执行了 count+=1 的操作,但是得到的结果不是我们期望的 2,而是 1。

我们潜意识认为的这个count+=1操作是一个不可分割的整体,就像一个原子一样,我们把一个或者多个操作在 CPU 执行的过程中不被中断的特性称为原子性。但实际情况就是不做任何处理的话,在并发情况下CPU进行切换,导致出现原子性的问题,我们一般通过加锁解决,这个不是本文的重点。

Java内存模型真面目

前面讲解并发的三大特性,其中原子性问题可以通过加锁的方式解决,那么可见性和有序性有什么解决的方案呢?其实也很容易想到,可见性是因为缓存导致,有序性是因为编译优化指令重排序导致,那么是不是可以让程序员按需禁用缓存以及编译优化, 因为只有程序员知道什么情况下会出现问题 顺着这个思路,就提出了JAVA内存模型(JMM)规范

Java 内存模型是 Java Memory Model(JMM),本身是一种抽象的概念,实际上并不存在,描述的是一组规则规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

默认情况下,JMM中的内存机制如下:

  • 系统存在一个主内存(Main Memory),Java 中所有变量都存储在主存中,对于所有线程都是共享的
  • 每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝
  • 线程对所有变量的操作都是先对变量进行拷贝,然后在工作内存中进行,不能直接操作主内存中的变量
  • 线程之间无法相互直接访问,线程间的通信(传递)必须通过主内存来完成

同时,JMM规范了 JVM 如何提供按需禁用缓存和编译优化的方法,主要是通过volatilesynchronizedfinal 三个关键字,那具体的规则是什么样的呢?

JMM 中的主内存、工作内存与 JVM 中的 Java 堆、栈、方法区等并不是同一个层次的内存划分,这两者基本上是没有关系的。

Happens-Before规则

JMM本质上包含了一些规则,那这个规则就是大家有所耳闻的Happens-Before规则,大家都理解了些规则吗?

Happens-Before规则,可以简单理解为如果想要A线程发生在B线程前面,也就是B线程能够看到A线程,需要遵循6个原则。如果不符合 happens-before 规则,JMM 并不能保证一个线程的可见性和有序性。

1.程序的顺序性规则

在一个线程中,逻辑上书写在前面的操作先行发生于书写在后面的操作。

这个规则很好理解,同一个线程中他们是用的同一个工作缓存,是可见的,并且多个操作之间有先后依赖关系,则不允许对这些操作进行重排序。

2. volatile 变量规则

指对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作。

怎么理解呢?比如线程A对volatile变量进行写操作,那么线程B读取这个volatile变量是可见的,就是说能够读取到最新的值。

3.传递性

这条规则是指如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C

这个规则也比较容易理解,不展开讨论了。

  1. 锁的规则

这条规则是指对一个锁的解锁 Happens-Before于后续对这个锁的加锁,这里的锁要是同一把锁, 而且用synchronized或者ReentrantLock都可以。

如下代码的例子:

synchronized (this) { // 此处自动加锁
  // x 是共享变量, 初始值 =10
  if (this.x < 12) {
    this.x = 12; 
  }  
} // 此处自动解锁
复制代码
  • 假设 x 的初始值是 8,线程 A 执行完代码块后 x 的值会变成 12(执行完自动释放锁)
  • 线程 B 进入代码块时,能够看到线程 A 对 x 的写操作,也就是线程 B 能够看到 x==12

5.线程 start() 规则

主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。

这个规则也很容易理解,线程 A 调用线程 B 的 start() 方法(即在线程 A 中启动线程 B),那么该 start() 操作 Happens-Before 于线程 B 中的任意操作。

6.线程 join() 规则

线程 A 中,调用线程 B 的 join() 并成功返回,那么线程 B 中的任意操作 Happens-Before 于该 join() 操作的返回。

使用JMM规则

我们现在已经基本讲清楚了JAVA内存模型规范,以及里面关键的Happens-Before规则,那有啥用呢?回到前言的问题中,我们是不是可以使用目前学到的关于JMM的知识去解决这个问题。

方案一: 使用volatile

根据JMM的第2条规则,主线程写了volatile修饰的run变量,后面的t线程读取的时候就可以看到了。

方案二:使用锁

利用synchronized锁的规则,主线程释放锁,那么后续t线程加锁就可以看到之前的内容了。

小结:

volatile 关键字

  • 保证可见性
  • 不保证原子性
  • 保证有序性(禁止指令重排)

volatile 修饰的变量进行读操作与普通变量几乎没什么差别,但是写操作相对慢一些,因为需要在本地代码中插入很多内存屏障来保证指令不会发生乱序执行,但是开销比锁要小。volatile的性能远比加锁要好。

synchronized 关键字

  • 保证可见性
  • 不保证原子性
  • 保证有序性

加了锁之后,只能有一个线程获得到了锁,获得不到锁的线程就要阻塞,所以同一时间只有一个线程执行,相当于单线程,由于数据依赖性的存在,单线程的指令重排是没有问题的。

线程加锁前,将清空工作内存中共享变量的值,使用共享变量时需要从主内存中重新读取最新的值;线程解锁前,必须把共享变量的最新值刷新到主内存中。

总结

本文讲解了JAVA并发的3大特性,可见性、有序性和原子性。从而引出了JAVA内存模型规范,这主要是为了解决并发情况下带来的可见性和有序性问题,主要就是定义了一些规则,需要我们程序员懂得这些规则,然后根据实际场景去使用,就是使用volatilesynchronizedfinal关键字,主要final关键字也会让其他线程可见,并且保证有序性。那么具体他们底层的实现是什么,是如何保证可见和有序的,我们后面详细讲解。

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

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

相关文章

一个简单的HTML网页 个人网站设计与实现 HTML+CSS+JavaScript自适应个人相册展示留言博客模板

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

Ubtunu排查磁盘空间是否已满—并清理的方式

项目场景&#xff1a; 最近使用nodejs开发的后端项目部署到Ubtunu服务器后接口无法访问了&#xff0c;接口也调用不通&#xff0c;NGINX报502错误。 问题描述 使用远程连接工具传文件也是无法上传&#xff0c;提示找不到文件&#xff0c;SCP命令也无法上传。 scp传文件报错&…

一文教会你如何在内网搭建一套属于自己小组的在线 API 文档?

Hello&#xff0c;大家好&#xff0c;我是阿粉&#xff0c;对接文档是每个开发人员不可避免都要写的&#xff0c;友好的文档可以大大的提升工作效率。 阿粉最近将项目的文档基于 Gitbook 和 Gitlab 的 Webhook 功能的在内网部署了一套实时的&#xff0c;使用起来特方便了。跟着…

第二证券|11月十大牛股出炉 特一药业163%涨幅问鼎榜首

到11月30日收盘&#xff0c;11月份十大牛股中7只个股涨幅超过100%&#xff0c;涨幅最小的也有87%&#xff0c;均匀涨幅较上个月有所扩大。 11月&#xff0c;A股出现震荡爬高态势&#xff0c;到11月30日收盘&#xff0c;上证指数月内涨8.91%&#xff0c;深证成指涨6.84%&#xf…

大学生HTML作业节日网页 HTML作业节日文化网页期末作业 html+css+js节日网页 HTML学生节日介绍 HTML学生作业网页视频

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

基于STM32单片机的温湿度检测报警器(数码管)(Proteus仿真+程序)

编号&#xff1a;27 基于STM32单片机的温湿度检测报警器&#xff08;数码管&#xff09; 功能描述&#xff1a; 本设计由STM32F103单片机最小系统DHT11温湿度传感器数码管显示模块声光报警模块独立按键组成。 1、主控制器是STM32F103单片机 2、DHT11传感器测量温度和湿度数据…

Android注解快速入门和实用解析

首先什么是注解&#xff1f;Override就是注解&#xff0c;它的作用是&#xff1a; 1、检查是否正确的重写了父类中的方法。2、标明代码&#xff0c;这是一个重写的方法。1、体现在于&#xff1a;检查子类重写的方法名与参数类型是否正确&#xff1b;检查方法private&#xff0f…

带你走进知识图谱的世界

知识图谱知识图谱的介绍01 什么是知识图谱02 知识图谱构建的关键技术03 知识图谱的存储04 知识图谱在金融领域的应用图数据库 Neo4j知识图谱的介绍 知识图谱最开始是Google为了优化搜索引擎提出来的&#xff0c;推出之后引起了业界轰动&#xff0c;随后其他搜索公司也纷纷推出…

[附源码]计算机毕业设计医疗器械公司公告管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

MXNet中im2rec将图片转换成二进制RecordIO格式

我们在前面的 计算机视觉之目标检测训练数据集&#xff08;皮卡丘&#xff09;《2》其中有提到这个二进制的RecordIO格式&#xff0c;使用的是MXNet自带的im2rec工具&#xff0c;源码在tools里面 import mxnet as mx #D:\Anaconda3\envs\myd2l\lib\site-packages\mxnet\tools\…

农大毕业7年后,转行软件测试,我拿到了一块结实的敲门砖

“经过这段时间的学习&#xff0c;让我摸清了这个行业的游戏规则&#xff0c;可以说是让我拿到了一块结实的敲门砖。” 今天跟大家分享的是近期就业的学员刘同学的转行故事&#xff0c;希望他的经历能给同样在向梦想奔跑的你一些帮助。 毕业7年后&#xff0c;从网信办主任转行…

基于STM32单片机的直流电机PWM调速(数码管显示)(Proteus仿真+程序)

编号&#xff1a;24 基于STM32单片机的直流电机PWM调速 功能描述&#xff1a; 由 STM32单片机数码管显示模块键盘模块L298N电机驱动模块直流电机 1、采用STM32F103单片机为主控制器 2、四个按键&#xff0c;分别为启动/暂停、方向切换、加速、减速功能 3、数码管显示PWM占空比…

[附源码]计算机毕业设计演唱会门票售卖系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

SMART PLC高速脉冲输出如何断电保持当前位置

S7-200 SMART PLC脉冲轴控制相关的内容,可以参看下面的博客: S7-200 SMART PLC自定义脉冲轴控功能块AxisControl_FB(梯形图)_RXXW_Dor的博客-CSDN博客博途1200/1500PLC的相关总线轴PN总线控制可以参考相关专栏的博客,链接地址如下:博途PLC 1200/1500PLC轴控功能块Servo_A…

[附源码]Python计算机毕业设计Django基于Vuejs的中国名茶销售平台

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

历史名人鲁迅介绍HTML个人网页作业作品下载 历史人物介绍网页设计制作 大学生英雄人物网站作业模板 dreamweaver简单个人网页制作

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

Java分布式系统和云计算教程

Java分布式系统和云计算教程 大规模学习分布式 Java 应用程序、并行编程、分布式计算和云软件架构 课程英文名&#xff1a;Distributed Systems & Cloud Computing with Java 此视频教程共4.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰无水印&#xff0c;源码附…

【数据可视化】Data Reduction和加利福尼亚的房价数据集数据可视化以及Kettle的初步介绍

一.数据归约Data Reduction 对海量数据进行复杂的数据分析和机器学习将需要很长时间&#xff0c;使得这种分析不现实或不可行。数据归约技术可以用来得到数据集的归约表示&#xff0c;它小得多&#xff0c;但仍接近保持原数据的完整性。对归约后的数据集计算将更有效&#xff…

[附源码]计算机毕业设计JAVA校园拓展活动管理系统

[附源码]计算机毕业设计JAVA校园拓展活动管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

[附源码]Python计算机毕业设计Django基于vue的软件谷公共信息平台

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…