【Java|多线程与高并发】volatile关键字和内存可见性问题

news2025/1/12 18:14:17

文章目录

  • 1.前言
  • 2. 编译器优化带来的内存可见性问题
  • 3. 使用volatile保证内存可见性
  • 5.volatile不能保证原子性
  • 以JMM的角度看待volatile
  • 总结

1.前言

synchronizedvolatile都是Java多线程中很重要的关键字,但它们的作用和使用场景有所不同。

synchronized关键字可以保证同一时刻只有一个线程可以访问被synchronized关键字保护的代码块,从而避免多个线程对共享资源的并发访问导致的数据不一致问题。

关于synchronized关键字更详细的介绍,可以参考我之前写的这篇文章线程安全问题以及synchronized使用实例

volatile用于保证变量在多个线程之间的可见性和有序性。

本文主要介绍valatite关键字

在这里插入图片描述

在介绍volatile关键字之前,先来认识一下编译器优化带来的问题.

2. 编译器优化带来的内存可见性问题

编译器优化是编译器在编译源代码时,对代码进行的一系列优化处理,以提高程序的运行效率和性能。

编译器优化的主要目标是在不改变程序功能的前提下,尽可能地减少程序的运行时间和内存占用。

但编译器优化在多线程环境下可能会造成内存可见性问题.

内存可见性问题:
当一个线程修改一个共享变量的值时,这个值会被保存在该线程的本地内存中,而不是直接写入主内存中。
如果其他线程需要读取该共享变量的值,它们可能会从自己的本地内存中读取旧值,而不是从主内存中读取最新的值,从而导致数据不一致的问题。

示例:

public class Demo9 {
    static class Counter{
        public  int count = 0;
    }
    public static void main(String[] args) {
        Counter counter = new Counter();

        Thread t1 = new Thread(()->{
            while(counter.count == 0){
               
            }
            System.out.println("t1 执行结束!");
        });
        t1.start();

        Thread t2 = new Thread(()->{
            Scanner scanner = new Scanner(System.in);
            System.out.print("> ");
            counter.count = scanner.nextInt();
        });
        t2.start();
    }
}

运行结果:
在这里插入图片描述
虽然counter.count的值修改成1了,但是t1的循环并没有结束. 为什么呢?

其实原因主要是在这里
在这里插入图片描述
counter.count == 0 这个操作会有两步,读内存(load)和进行比较(cmp).加上这里的条件是while,那么此时读内存和进行比较就会执行很多次.

编译器就会对上述代码进行优化,读内存比进行比较这个操作慢得多.
既然频繁读内存,且每次读内存后的值都是一样的,那么就没必要多次读内存了.只读一次后面就直接读本地内存中的值(提高效率).

因此在进行修改counter.count的值之前,t1线程就已经读过counter.count的值了,t2修改了但t1并没有感知到. 这也就是编译器优化带来的内存可见性问题

3. 使用volatile保证内存可见性

内存可见性是指多个线程之间共享变量时,对变量的修改能够被其他线程及时地看到。

当一个变量被声明为volatile时,每次读取该变量时,都会从主内存中读取最新的值,而不是从线程的本地内存中读取。同样,每次写入该变量时,都会立即将值刷新到主内存中,而不是仅在线程的本地内存中修改。

接下来就可以通过volatile关键字解决上述的问题
在这里插入图片描述
只需在count变量前加上volatile即可.

运行结果:
在这里插入图片描述
可以看到加上volatile关键字之后,修改count的值,t1就能够"感知"到了.

上述就是简单的使用volatile保证内存可见的简单案例

但其实编译器优化导致的内存可见性问题,也并不是一定就会发生.
在这里插入图片描述
如果让t1线程这里的while里面加一个线程休眠2s这段代码,此时即使不加volatile关键字,也不会导致内存可见性问题.

在这里插入图片描述
这里为什么不会产生内存可见性呢?

2s对于我们人来说,很短.但是对于计算机来说却是很漫长的.
别忘了编译器优化的目的,编译器优化主要是为了提高效率.
休眠2s,编译器即使优化了也没有什么提升.

5.volatile不能保证原子性

原子性是指操作或事务的不可分割性和不可中断性

就以两个线程针对同一个变量,同时进行修改操作为例:

class Counter{
    public volatile int count;
    public void add(){
        count++;
    }
}
public class Demo10 {
    private static Counter counter = new Counter();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        Thread t2 = new Thread(()->{
            for (int i = 0; i < 50000; i++) {
                counter.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = "+counter.count);
    }
}

运行结果:
在这里插入图片描述
其实之前谈synchronized的时候,就说过这个问题.

count++这个操作可以分为3步:
在这里插入图片描述
因此count++并不是原子性. 我们需要使用synchronized来保证原子性.但是volatile并不能保证原子性

以JMM的角度看待volatile

JMM是Java内存模型(Java Memory Model)的简称,是一种规范,用于规定Java虚拟机(JVM)如何与计算机内存交互,以及多线程如何访问共享内存。

volatile禁止了编译器优化,避免了直接读取缓存(工作内存)中的数据,而是每次都去读取主内存中的数据.

以JMM的视角看待volatile:
正常程序运行时,会把主内存中的数据加载到工作内存中,在进行计算处理.
编译器优化可能会导致读到的数据来自于工作内存,而不是主内存
volatile的效果就是保证每次读到的数据都是从主内存读到的

总结

volatile关键字主要用于保证可见性和有序性,但不能保证原子性。
适用场景:

  1. 变量被多个线程共享,且其中一个线程修改了该变量的值,需要让其他线程立即看到该修改。
  2. 变量的值在程序中的读写顺序很重要,需要保证操作的有序性。

volatile会禁止编译器和JVM对代码进行优化,增加了内存的读写操作,降低了程序的执行效率。
在这里插入图片描述

感谢你的观看!希望这篇文章能帮到你!
专栏: 《从零开始的Java学习之旅》在不断更新中,欢迎订阅!
“愿与君共勉,携手共进!”
在这里插入图片描述

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

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

相关文章

Linux之文件打包和解压缩

任务描述 有时&#xff0c;我们会在Linux系统中将多个文件打包成一个单独的文件&#xff0c;通过本关的学习&#xff0c;我们将学会如何在Linux系统中将多个文件/目录打包生成一个文件。 本关任务&#xff1a;使用tar命令完成文件和目录的打包操作。 相关知识 tar&#xff…

验证断言(立即断言并行断言)

目录 1.何为断言 2.断言的作用&#xff1a; 3.断言的种类 3.1立即断言 3.2并发断言 4.断言层次结构 4.1 sequence 序列 4.2 property 序列 5.sequence和property的异同 6.补充知识点&#xff08;assert/cover/assume&#xff09; 7.写在后边 1.何为断言 断言主要…

网络知识点之-FTP协议

FTP协议指文件传输协议&#xff08;File Transfer Protocol&#xff0c;FTP&#xff09;&#xff0c;是用于在网络上进行文件传输的一套标准协议&#xff0c;它工作在 OSI 模型的第七层&#xff0c; TCP 模型的第四层&#xff0c; 即应用层&#xff0c; 使用 TCP 传输而不是 UD…

第一节 初识C语言

第一节 初识C语言 目录 一&#xff0e; 什么是C语言二&#xff0e; 第一个C语言程序三&#xff0e; 数据类型四&#xff0e; 变量与常量五&#xff0e; 未完待续 本章重点&#xff1a; 什么是C语言第一个C语言程序数据类型变量、常量字符串转义字符注释选择语句循环语句函数数组…

【LeetCode】每日一题 -- 1171. 从链表中删去总和值为零的连续节点 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/remove-zero-sum-consecutive-nodes-from-linked-list/ 1. 题解&#xff08;1171. 从链表中删去总和值为零的连续节点&#xff09; 2021年字节二面真题 1.1 暴力解法&#xff1a;穷举 时间复杂度 O(n2)&#xff0c;空间复杂…

Python系列之面向对象编程

目录 一、面向对象编程 1.1 面向对象三大特征 1.2 什么是对象 二、类(class)和实例(instance) 2.1 类的构成 2.2 创建类 2.3 创建实例对象和访问属性 2.4 Python内置类属性 2.5 类属性与方法 三、类的继承 3.1 方法重写 四、多态 一、面向对象编程 1.1 面向对象三大…

抖音短视频矩阵系统-源码-系统搭建

目录 1. 短视频AI智能创作 2. 托管式账号管理: 3. 数据分析 4. 智能营销获客 开发流程 抖音账号矩阵系统开发&#xff0c;抖音账号矩阵系统源码搭建&#xff0c;抖音账号技术系统源码部署 抖音矩阵系统专注于为短视频私域运营达人或企业提供一站式赋能服务平台。具体包括智…

小议CSDN周赛57期 - 凑数

本期周赛几乎忘记参加&#xff0c;在最后几分钟的时候上来看了看。那些选择判断一通乱选&#xff0c;填空题也已经被吐槽得差不多了&#xff0c;这里不多说&#xff0c;只说我对第一道编程题的看法&#xff08;吐槽&#xff09;。因为 C 站的机制是&#xff0c;即使它错了&…

彻底理解HTTPS加密原理

目录 1.为什么需要加密&#xff1f; 2.什么是对称加密&#xff1f; 3.什么是非对称加密&#xff1f; 4.非对称加密对称加密&#xff1f; 5.数字证书 6.数字签名 相信大家对于HTTP与HTTPS的区别都有了解&#xff0c;那么对于HTTPS的加密过程你是否知道呢&#xff1f; 对称…

单片机内存管理

单片机内存管理 1、随机存储器 RAM是随机存储器&#xff0c;读写速度快&#xff0c;但掉电以后数据会丢失。它分为SRAM(静态RAM)和DRAM(动态RAM)。SRAM无需刷新就可以保存数据&#xff1b;DRAM需要不断刷新才可以保存数据。在CPU内部的RAM&#xff0c;就叫内部RAM&#xff0c…

算法模板(3):搜索(4):高等图论

高等图论 有向图的强连通分量 相关概念 强连通分量&#xff1a;Strongly Connected Component (SCC).对于一个有向图顶点的子集 S S S&#xff0c;如果在 S S S 内任取两个顶点 u u u 和 v v v&#xff0c;都能找到一条 u u u 到 v v v 的路径&#xff0c;那么称 S S…

JVM零基础到高级实战之Java程序员不可不知的对象创建底层步骤细节

JVM零基础到高级实战之Java程序员不可不知的对象创建底层步骤细节 JVM零基础到高级实战之Java程序员不可不知的对象创建底层步骤细节 文章目录 JVM零基础到高级实战之Java程序员不可不知的对象创建底层步骤细节前言Java对象创建的流程步骤包括哪些&#xff1f;总结 前言 JVM零…

【云原生 | 53】Docker三剑客之Docker Compose应用案例一:Web负载均衡

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 &#x1f3c5;阿里云ACE认证高级工程师 &#x1f3c5;阿里云开发者社区专…

基于Echarts构建停车场数据可视化大屏(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

【部署LVS-DR 群集】

目录 一、DR模式 LVS负载均衡群集1、数据包流向分析2、DR 模式的特点 二、DR模式 LVS负载均衡群集部署1、1.配置负载调度器&#xff08;192.168.80.30&#xff09;&#xff08;1&#xff09;配置虚拟 IP 地址&#xff08;VIP&#xff1a;192.168.102.188&#xff09;&#xff0…

《设计模式》之装饰器模式

文章目录 1、定义2、动机3、类结构4、优缺点5、注意事项6、总结7、代码实现(C) 1、定义 动态&#xff08;组合&#xff09;地给一个对象增加一些额外的职责。就增加功能而言&#xff0c;Decorator模式比生成子类&#xff08;继承&#xff09;更为灵活&#xff08;消除重复代码…

PPT中这8个隐藏技巧-掌握了马上让你幸福感满满

开篇 一个好的PPT需要精雕细琢。即使我们使用了AIGC特别是时下流行的用GPT书写大纲,然后把大纲内的内容放到一些自动GC PPT内容的生成器里生成后的PPT其实也不是马上可以拿来用的。工作上一份大领导、公司、集团级别的PPT不可能90%使用GPT GC生成就可以直接交付的。比如说我们…

Trie树模板与应用

文章和代码已经归档至【Github仓库&#xff1a;https://github.com/timerring/algorithms-notes 】或者公众号【AIShareLab】回复 算法笔记 也可获取。 文章目录 Trie树&#xff08;字典树&#xff09;基本思想例题 Trie字符串统计code关于idx的理解 模板总结应用 最大异或对分…

初探BERTPre-trainSelf-supervise

初探Bert 因为一次偶然的原因&#xff0c;自己有再次对Bert有了一个更深层地了解&#xff0c;特别是对预训练这个概念&#xff0c;首先说明&#xff0c;自己是看了李宏毅老师的讲解&#xff0c;这里只是尝试进行简单的总结复述并加一些自己的看法。 说Bert之前不得不说现在的…

ansible远程执行指令,/bin/sh: java: command not foundnon-zero return code

问题描述&#xff1a;ansible远程执行指令&#xff0c;初选指令加载不全&#xff0c; [rootVM-0-6-centos ~]# ansible all -m shell -a "java -version" 10.206.0.15 | FAILED | rc127 >> /bin/sh: java: command not foundnon-zero return code 解决方案&a…