【JavaEE初阶 — 多线程】内存可见性问题 volatile

   c96f743646e841f8bb30b2d242197f2f.gif

ddb5ae16fc92401ea95b48766cb03d96.jpeg692a78aa0ec843629a817408c97a8b84.gif

  1. 内存可见性问题  


   内存可见性的概念  


  什么是内存可见性问题呢?  

  • 当一个线程对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。
  • 在Java中,可以借助 synchronized、volatile 以及各种Lock 实现可见性。
  • 如果我们将变量声明为 volatile,这就指示JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取,不能优化。

   编译器优化   


造成内存可见性问题的原因,是因为编译器优化的机制而造成的,我们先介绍一下什么是编译器优化。


   背景   


为什么要有编译器优化这样的机制呢?由于程序员的水平参差不齐,研究JDK 的大佬们,就希望通过让 编译器 & JVM 对程序员写的代码,自动的进行优化;


    优点    


  • 对于程序员原有的代码,编译器/JVM会在原有逻辑不变的前提下,对代码进行调整,使程序效率更高;这样的操作,就是编译器优化。
  • 也就是说,我们写的代码,编译器会在原有逻辑上,帮我们调整代码,通过编译器优化,可保证原有逻辑不变的前提下,对代码进行修改,使得执行效率得到提高。

    缺点   


  • 编辑器在编译代码的时候,其实它并没有执行代码,它只是根据这个编译的一个静态的代码,来分析这个程序应该怎么去进行调整;所以编译器 “保证原因逻辑不变,再进行优化 ”,这样的 “保证” 并非是能够 100% 生效的;
  • 尤其是很多线线程的程序中,因为并发编程随机调度的特点,使得执行多线程程序的过程中,可能会出现诸多变数,这些变数可能会导致编译器出现判断失误;
  • 因此,编译器在针对不同的程序,能够做出的判断是有限,并且容易失误的。所以在经过编译器优化后的代码逻辑,与优化前的逻辑,可能会出现偏差。

   案例描述  


 内存可见性问题是造成线程安全问题的原因之一,我们通过下面的代码来感受一下,内存可见性是如何造成线程安全问题的:

   代码逻辑:


  • 我们定义一个成员变量 flag ,用来作为 t1 线程 run() 方法中的循环终止条件;
  • t2 线程用来修改 flag 的值;
  • 这样的操作,相当于一个线程进行读取,另一个线程进行修改

   原子性问题和可见性问题代码演示的区别   


  • 这里演示因为内存可见性,造成线程安全问题,是令一个线程进行读取另一个线程进行修改
  • 演示因为操作非原子性,而造成线程安全问题,是令两个线程同时修改同一个变量,所以两个线程都是在进行修改操作;

   预期效果:


  • 只要我们通过 t2 ,输入给 flag 的数字是一个非零的值,就会使得我们 t1 线程的循环能够结束;

  程序运行结果:

但是当我真正输入一个非零值的时候,回车,发现 t1 线程并没有结束循环,打印结束日志


   通过 Jconsole 观察 t1 线程的状态   

  • 因为 t2 线程只有一次输入修改 flag 的操作,已经终止;  
  • 观察到 t1 的线程状态是Runnable,正在持续执行循环;
  • 在 t2 线程输入非零值,能让 t1 线程循环结束,进而 t1 终止,这是我们预期结果;
  • 但是实际执行结果却并非如此;
  • 一个线程读取,一个线程修改,t2(修改线程) 修改的值,并没有被 t1(读取线程)读到,这就是因为编译器优化而造成的"内存可见性问题”;

  分析出现内存可见性问题的原因  

对于上述代码中,t1 线程的循环判断条件 flag== 0,对其进行细分,会分出两个指令,分别是比较指令(==)和读取指令(读flag);

程序会先执行读取指令 load ,只有把 flag 这个变量在内存中的值,读取到寄存器中,才会执行比较指令 cmp;

而因为 load,cmp 两步指令是在循环中完成的,while 循环如果没有休眠限制,会在短时间内循环多次,从而重复执行多次 load -> cmp 这样的指令。

但是,load (读内存操作)和 cmp (纯CPU寄存器操作)两步指令的开销是非常大的;

load 的时间开销是 cmp 的几千倍,因为虽然读内存数据比读硬盘数据要快很多,但是如果是拿CPU寄存器和内存比,那就是寄存器快很多;

因此,在 t1 创建好后,run() 方法执行的时间,几乎都在load,cmp 的时间开销是可以忽略不计的;

所以在执行的过程中,JVM就能感知到,load 反复执行的结果是一样的;哪怕我们通过 t2 的 scanner 输入 flag 的时间只有不到 1s,但是站在计算机的角度,这 1s 可以说是沧海桑田

因此,程序在执行的过程中,JVM 会感受到程序一直在反复读内存的值;

为了减小时间开销, t1 线程的读操作,会被编译器优化成:从读内存的值,到读CPU寄存器(t1 线程的工作内存)的值;

后续再执行 load 指令,就不会再重新读内存,而是直接从寄存器(工作内存)中读取,从而大大减小开销,并且提高了效率;

于是,等到很多秒后,用户真正输入新的值,真正修改 flag 的值,此时 t1 线程就感知不到了

(编译器优化,使得 t1 线程的读操作,不是读取内存)


   2. JMM模型文档    

JMM(Java 内存模型)详解


   3. volatile   


volatile 能保证内存可见性


内存可见性就是保证, 每次去读取的时候,  读取到的值都是最新的值(内存中的值),而不是之前缓存在寄存器中的值;volatile 修饰的变量,能够保证"内存可见性";


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

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

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

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

前面我们讨论内存可见性时说了,直接访问工作内存(实际是CPU 的寄存器或者 CPU 的缓存),速度
非常快,但是可能出现数据不一致的情况;


加上 volatile,强制读写内存,速度是慢了,但是数据变的更准确了:


  c96f743646e841f8bb30b2d242197f2f.gif

692a78aa0ec843629a817408c97a8b84.gif

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

排序算法.

排序算法是最常用的一种算法.它解决的主要问题是在一定的时间复杂度和空间复杂度的条件下,对n个数按照一定的顺序进行排序.排序算法主要分为四大类,即插入类,交换类,选择类和归并类,不同的排序算法的时间复杂程度和空间复杂程度差别很大. 排序算法主要有以下几种: 1.插入类排…

iOS18.1通話錄音實測 錄音夠清晰 文字轉錄廣東話用唔到?

iOS 18.1功能實測|期待已久的通話錄音功能終在iOS18.1推出,讓用家可以在通話過程中輕鬆錄音,並附上逐字稿功能,為使用者提供更靈活的通話記錄方式。記者實測通話錄音功能,看看錄音清晰度、方便性、逐字轉錄的表現。 打…

403 Request Entity Too Lager(请求体太大啦)

昨天收到 QA 的生产报障,说是测试环境的附件上传功能报了 403 的错误,错误信息:403 Request Entity Too Lager。我尝试复现问题,发现传个几兆的文件都费劲啊,一传一个失败。不用说,项目用到 ng 代理&#x…

【C++】新手入门指南

> 🍃 本系列为初阶C的内容,如果感兴趣,欢迎订阅🚩 > 🎊个人主页:[小编的个人主页])小编的个人主页 > 🎀 🎉欢迎大家点赞👍收藏⭐文章 > ✌️ 🤞 &#x1…

ElasticSearch备考 -- Cross cluster replication(CCR)

一、题目 操作在cluster1(local)中操作索引task,复制到cluster2(remote)中 二、思考 CCR 我们可以对标MySQL 理解为为主从,后者备份。主节点负责写入数据,从/备节点负责同步时主节点的数据。 …

IDEA在编译时: java: 找不到符号符号: 变量 log

一、问题 IDEA在编译的时候报Error:(30, 17) java: 找不到符号符号: 变量 log Error:(30, 17) java: 找不到符号 符号: 变量 log 位置: 类 com.mokerson.rabbitmq.config.RabbitMqConfig 二、解决方案 背景:下载其他同事代码时,第一次运行&#xff0c…

一文熟悉新版llama.cpp使用并本地部署LLAMA

0. 简介 最近是快到双十一了再给大家上点干货。去年我们写了一个大模型的系列,经过一年,大模型的发展已经日新月异。这一次我们来看一下使用llama.cpp这个项目,其主要解决的是推理过程中的性能问题。主要有两点优化: llama.cpp …

[翻译]ANSI X9.24-3-2017

目录 1 目的 2 范围 2.1 应用 3 参考文献 4 术语和定义 4.1 高级加密标准(AES) 4.2 AES 4.3 算法 4.4 ANSI 4.5 基础推导密钥(BDK) 4.6 BDK 4.7 BDK ID 4.8 加密密钥 4.9 加密密钥同步 4.10 密码强度 4.11 派生 4.12 派生标识符(ID) 4…

使用 GitHub Actions 部署到开发服务器的详细指南

使用 GitHub Actions 部署到开发服务器的详细指南 在本篇博客中,我们将介绍如何使用 GitHub Actions 实现自动化部署,将代码从 GitHub 仓库的 dev 分支自动部署到开发服务器。通过这种方式,可以确保每次在 dev 分支推送代码时,服…

Docker安装部署RabbitMQ

1. Docker环境准备 1.1 安装Docker 在开始Docker安装部署RabbitMQ之前,确保您的系统环境已经满足Docker的运行要求。以下是在不同操作系统上安装Docker的步骤和命令行演示。 对于Linux系统 在基于Debian的系统(如Ubuntu)上,您…

UniAPP u-popup 禁止背景滑动

增加class .NoScroll {overflow: hidden;position: fixed; }在外层div上增加该class判断条件

ubuntu 24.04运行chattts时cuda安装错误原因分析

使用ubuntu 24.04,按照2noise/ChatTTS官方流程安装依赖时报错。ChatTTShttps://github.com/2noise/ChatTTS 这是因为cuda版本不对,ChatTTS目前的版本,要求支持cuda 12.4及以上,但是如果nvidia显卡驱动版本较老,无法支…

spring-security(记住密码,CSRF)

注册PersistentTokenRepository PersistentTokenRepository实现类 InMemoryTokenRepositoryImpl基于内存实现 JdbcTokenRepositoryImpl基于数据库实现 基于内存实现 Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { Bean publi…

iOS问题记录 - 503 Service Temporarily Unavailable

文章目录 前言开发环境问题描述问题分析解决方案最后 前言 最近有个项目经历了大改动,本地测试没什么问题,于是准备通过打包机打包用于内部测试的包,然后问题就来了。 开发环境 Xcode: 16.1Fastlane: 2.219.0 问题描述 问题出在登录苹果…

linux-vlan

# VLAN # 1.topo # 2.创建命名空间 ip netns add ns1 ip netns add ns2 ip netns add ns3 # 3.创建veth设备 ip link add ns1-veth0 type veth peer name ns21-veth0 ip link add ns3-veth0 type veth peer name ns23-veth0 # 4.veth设备放入命名空间,启动接口 ip link set n…

HTB:Precious[WriteUP]

目录 连接至HTB服务器并启动靶机 使用nmap对靶机TCP端口进行开放扫描 使用curl访问靶机80端口 使用ffuf爆破一下子域 使用浏览器访问该域名 使用curl访问该域名响应头 使用exiftool工具查看该pdf信息 横向移动 USER_FLAG:adf5793a876a190f0c08b3b6247cec32…

链表归并与并集相关算法题

两递增归并为递减到原位 假设有两个按元素递增次序排列的线性表,均以单链表形式存储。将这两个单链表归并为一个按元素递减次序排列的单链表,并要求利用原来两个单链表的节点存放归并后的单链表 算法思想 因为两链表已按元素值递增次序排列&#xff0…

山东布谷科技:关于直播源码|语音源码|一对一直播源码提交App Store的流程及重构建议

自从YY、六间房开启国内聊天室和秀场等网红盛行的网络红利时代以来,紧随其后国内各大音视频平台相应出现,先有映客花椒等直播平台的风头正劲,后有功能板块更丰富的头条抖音Tiktok等,盈利功能点不仅仅有直播PK连麦等礼物打赏功能&a…

C++ 【STL容器系列(一)】vector的使用

1.介绍 vector是STL中的容器之一( STL(standard template libaray-标准模板库):是C标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。),STL的容器有非常多&#x…

机器学习5_支持向量机_原问题和对偶问题——MOOC

目录 原问题与对偶问题的定义 定义该原问题的对偶问题如下 在定义了函数 的基础上,对偶问题如下: 综合原问题和对偶问题的定义得到: 定理一 对偶差距(Duality Gap) 强对偶定理(Strong Duality Theo…