内存可见性与指令重排序

news2025/2/27 8:41:02

文章目录

  • 内存可见性
    • 内存可见性问题代码演示
    • JMM(Java Memory Model)
  • 指令重排序
    • 指令重排序问题代码演示
    • 指令重排序分析
  • volatile关键字
    • volatile 保证内存可见性 & 禁止指令重排序
    • volatile 不保证原子性

在上一节介绍线程安全问题的过程中,提到了产生线程安全的原因主要有

  1. 操作系统的线程随机调度策略
  2. 对共享数据的写操作
  3. 操作不具有原子性
  4. 内存可见性问题
  5. 指令重排序问题

这五点原因中线程的随机调度是由操作系统调度模块具体实现,无法干预,而多个线程对共享数据的写操作,在某些情况下可以通过调整代码结构进行避免。操作的原子性,可以通过加锁来解决,这小节我们主要来看内存可见性和指令重排序是怎样影响到线程安全的.

由于读取内存,相比较读取寄存器是一个非常慢的操作,编编译器为了进一步提高代码执行的效率,会在保持逻辑不变的前提下,调整生产的代码内容,这样的操作在单线程环境中不会有什么问题,但是,在多线程环境下,编译器就可能会误判,内存可见性和指令重排序都是有编译器优化产生的问题

内存可见性

内存可见性问题代码演示

我们先来观察这段代码:

import java.util.Scanner;


public class Test6 {
    public static int isQuit = 0;

    // 内存可见性问题
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (isQuit == 0) {
                // 什么也不执行
            }
            System.out.println("t1 线程执行完毕");
        });

        Thread t2 = new Thread(() -> {
            System.out.println("请输入isQuit:");
            Scanner scanner = new Scanner(System.in);
            isQuit = scanner.nextInt();
            System.out.println("t2线程执行完毕");
        });
        // 启动线程
        t1.start();
        t2.start();
    }
}

执行结果~~

请输入isQuit:
1
t2线程执行完毕

可以看到这里,输入1之后线程并没有执行完毕,那么不应该啊,isQuit的值不为0,t1线程应该会退出循环,可是并没有。我们看一张图。
在这里插入图片描述
在这个过程中,我么看看两个线程都做了什么。t1 线程在一直在读取主内存中isQuit的值,由于循环体没有执行任何逻辑,所以这个速度非常之快。t2线程先将isQuit读入工作内存,然后修改值为1后写回主内存。

如果就这样看,那么在isQuit的值被修改后t1线程也应该随之终止。但事实上Java在运行时,编译器发现在大量读取isQuit的值后,发现isQuit的值并没有改变。于是就做出来一种激进的优化(读取内存要比读取寄存器慢得多),不再读取内存,直接从寄存器中取值,这就导致了后续t2线程在我们输入值后,isQuit的值的确是改变了,但是t1线程并没有取读取内存中的isQuit,这就导致了t1线程对isQuit的内存不可见

在单线程中,编译器这样的优化一般是没有问题的,但是在并发场景下,就不得不考虑这样优化后对代码的影响。于是Java提供了volatile关键字,被这个关键字修饰后,编译器将不会进行优化。

JMM(Java Memory Model)

我们先了解一下JMM, Java虚拟机(JVM)规范文档中定义了Java内存模型.。目的是屏蔽掉各种硬件和操作系统的内存访问差异(跨平台),以实现让Java程序在各种平台下都能达到一致的并发效果。

  • 线程之间的共享变量存在 主内存 (Main Memory) - 相当于内存
  • 每一个线程都有自己的 “工作内存” (Work Memory) - 相当于寄存器
  • 当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据.
  • 当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存.

指令重排序

     和内存可见性一样,指令重排序也是在一定条件下触发的编译器的”优化“,目的是提高代码效率,编译器在“保持逻辑不发生变化的情况下”,针对指令执行的顺序进行调整,这就是指令重排序。

指令重排序问题代码演示

class SingletonLazy {
    private static volatile SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy() { }
}

public class Demo22 {
    public static void main(String[] args) {

    }
}

在这个单例模式(懒汉模式)中,如果是第一次创建实例,那么会涉及到一个new操作。我们简单的将new操作理解为三步:

  1. 申请内存空间
  2. 在内存空间上构造对象
  3. 把内存地址,复制给instance引用

指令重排序分析

在单线程下,先执行指令2,还是先执行指令3都可以,不影响最终的结果,但是在多线程下,就可能会出现问题。假设编译器将new操作的执行顺序优化为了 1 -> 3 -> 2,t1线程进入,创建单例,但是还没构造对象,就已经将空引用返回(锁已经释放),这是如果t2线程进入,instance还是为空此时就可能会创建出多个实例。

解决方案和内存可见性一样,使用volatile关键字,让编译器不要进行优化

volatile关键字

volatile 保证内存可见性 & 禁止指令重排序

代码在写入 volatile 修饰的变量时

  • 改变线程工作内存中volatile变量副本的值
  • 将改变后的副本的值从工作内存刷新到主内存

代码在读取 volatile 修饰的变量时

  • 从主内存中读取volatile变量的最新值到线程的工作内存中
  • 从工作内存中读取volatile变量的副本

    读取内存相较于读取寄存器来说,非常慢,使用volatile修饰虽然强制读写内存,但是保证了代码的正确性,一般来说,不会牺牲正确新来换取效率。

volatile 不保证原子性

volatile 和 synchronized 有着本质的区别. synchronized 能够保证原子性,volatile保证的是内存可见性,禁止指令重排序。volatile只是强制cpu读取内存,但是不会保证操作的原子性(不可分割)。

不管是原子性、内存可见性还是指令重排序,都可能产生线程安全问题,我们在进行并发编程时一定要谨慎!!

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

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

相关文章

常用通信接口、协议:SCCB

一、概述 SCCB(串行摄像头控制总线)是由欧姆尼图像技术公司(OmniVision)开发的一种类IIC的总线,主要用于其OV系列的图像传感器上(但目前有很多家的图像传感器都有采用该控制总线)。相对于IIC总线来说SCCB与之最主要的差…

【计算思维】蓝桥杯STEMA 科技素养考试真题及解析 2

1、兰兰有一些数字卡片,从 1 到 100 的数字都有,她拿出几张数字卡片按照一定顺序摆放。想一想,第 5 张卡片应该是 A、11 B、12 C、13 D、14 答案:C 2、按照下图的规律,阴影部分应该填 A、 B、 C、 D、 答案&am…

安防监控视频融合平台EasyCVR定制化页面开发

安防监控EasyCVR视频汇聚平台基于云边端智能协同,支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防视频平台EasyCVR拓展性强,视频能力丰富,具体可实现视频监控直播、视频轮播、视频录像、云存储、回放与检索…

让SOME/IP运转起来——SOME/IP系统设计(下)之数据库开发

上一篇我们介绍了SOME/IP矩阵的设计流程,这一篇重点介绍如何把SOME/IP矩阵顺利的交给下游软件团队进行开发。 车载以太网通信矩阵开发完成后,下一步应该做什么? 当我们完成SOME/IP矩阵开发,下一步需要把开发完成的矩阵换成固定格…

Web项目从Tomcat迁移到TongWeb

注意事项 1. 使用JNDI方式获取数据源: ①在TongWeb创建JDBC连接池; ②修改Web项目数据源配置. #spring.datasource.urljdbc:mysql://127.0.0.1:3306/demo #spring.datasource.usernametest #spring.datasource.passwordspring.datasource.jndi-namedemo2. 修…

485 实验

485(一般称作 RS485/EIA-485)隶属于 OSI 模型物理层,是串行通讯的一种。电气特性规定 为 2 线,半双工,多点通信的类型。它的电气特性和 RS-232 大不一样。用缆线两端的电压差值 来表示传递信号。RS485 仅仅规定了接受端和发送端的电气特性。它…

(动手学习深度学习)第13章 实战kaggle竞赛:狗的品种识别

文章目录 1. 导入相关库2. 加载数据集3. 整理数据集4. 图像增广5. 读取数据6. 微调预训练模型7. 定义损失函数和评价损失函数9. 训练模型 1. 导入相关库 import os import torch import torchvision from torch import nn from d2l import torch as d2l2. 加载数据集 - 该数据…

论文《Unsupervised Dialog Structure Learning》笔记:详解DD-VRNN

D-VRNN模型和DD-VRNN模型 总体架构 离散-可变循环变分自编码器(D-VRNN)和直接-离散-可变循环变分自编码器(DD-VRNN)概述。D-VRNN和DD-VRNN使用不同的先验分布来建模 z t z_t zt​之间的转换,如红色实线所示。 x t x_t…

爱创科技总裁谢朝晖荣获“推动医药健康产业高质量发展人物”

中国医药市场规模已经成为全球第二大医药市场,仅次于美国。近年来,随着中国经济的持续增长和人民生活水平的提高,医药市场需求不断扩大。政府对医疗卫生事业的投入也在不断加大,为医药行业的发展创造了良好的政策环境。为推动医药…

基于顺序表实现通讯录

1.功能实现 功能要求 1)至少能够存储100个人的通讯信息 2)能够保存用户信息:名字、性别、年龄、电话、地址等 3)增加联系人信息 4)删除指定联系人 5)查找制定联系人 6)修改指定联系人 7&#xf…

Sentinel 监控数据持久化(mysql)

Sentinel 实时监控仅存储 5 分钟以内的数据,如果需要持久化,需要通过调用实时监控接口来定制,即自行扩展实现 MetricsRepository 接口(修改 控制台源码)。 本文通过使用Mysql持久化监控数据。 1.构建存储表&#xff08…

java-String

String 1. String引入 1.1 构造方法 public static void main1(String[] args) {//构造方法String s1 "hello world";String s2 new String("yuanwei");char[] values {a,b,c};String s3 new String(values);System.out.println(s1);System.out.printl…

看不惯AI版权作品被白嫖!Stability AI副总裁选择了辞职,曾领导开发Stable Audio

近日,OpenAI的各种大瓜真是让人吃麻了。 而就在Sam Altmam被开除前两天,可能没太多人注意到Stability AI副总裁Newton—Rex因看不惯StabilityAI在版权保护上的行为选择辞职一事。 大模型研究测试传送门 GPT-4传送门(免墙,可直接…

记录一次因内存不足而导致hiveserver2和namenode进程宕机的排查

背景 最近发现集群主节点总有进程宕机,定位了大半天才找到原因,分享一下 排查过程 查询hiveserver2和namenode日志,都是正常的,突然日志就不记录了,直到我重启之后又恢复工作了。 排查各种日志都是正常的&#xff0…

windows搭建gitlab教程

1.安装gitlab 说明:由于公司都是windows服务器,这里安装以windows为例,先安装一个虚拟机,然后安装一个docker(前提条件) 1.1搜索镜像 docker search gitlab #搜索所有的docker search gitlab-ce-zh #搜索…

【css】Google第三方登录按钮样式修改

文章目录 场景前置准备修改样式官方属性修改样式CSS修改样式按钮的高度height和border-radiusLogo和文字布局 场景 需要用到谷歌的第三方登录,登录按钮有自己的样式。根据官方文档:概览 | Authentication | Google for Developers,提供两种第…

SPASS-ARIMA模型

基本概念 在预测中,对于平稳的时间序列,可用自回归移动平均(AutoRegres- sive Moving Average, ARMA)模型及特殊情况的自回归(AutoRegressive, AR)模型、移动平均(Moving Average, MA)模型等来拟合,预测该时间序列的未来值,但在实际的经济预测中,随机数据序列往往…

HarmonyOS ArkTS Video组件的使用(七)

概述 在手机、平板或是智慧屏这些终端设备上,媒体功能可以算作是我们最常用的场景之一。无论是实现音频的播放、录制、采集,还是视频的播放、切换、循环,亦或是相机的预览、拍照等功能,媒体组件都是必不可少的。以视频功能为例&a…

6-使用nacos作为注册中心

本文讲解项目中集成nacos,并将nacos作为注册中心使用的过程。本文不涉及nacos的原理。 1、项目简介 以一个演示项目为例,项目包含三个服务,调用及依赖如下图: 由图中可以看出,coupon-customer-serv为服务的消费者&a…

Python基础教程: sorted 函数

嗨喽,大家好呀~这里是爱看美女的茜茜呐 sorted 可以对所有可迭代的对象进行排序操作, sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。 从新排序列表。 👇 👇 👇 更多精彩机密、教程…