Java并发—volatile关键字的作用及使用场景

news2024/12/28 21:00:18

在这篇文章Java并发—Java内存模型以及线程安全-CSDN博客多次提及volatile关键字,这是一个非常重要的概念,主要用于多线程编程中,它确保了变量的可见性和禁止指令重排序,但不保证原子性,下面详细解释volatile关键字的作用和特性:

1、volatile关键字的两层语义

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)保证可见性

保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。


上篇文中有讲一个例子:🌰线程A和线程B从主内存读取和修改x=1的过程

419f7d591bdf461fa9f107e3838ff3e4.png

由于线程A对变量的修改x=2未立即对线程B可见,造成了线程安全问题,为了确保线程间的即时通信和数据一致性,使用 volatile 关键字是必要的

如果在用volatile修饰变量x后,都会从主内存中读取最新的最新的值,而不是从线程自己的工作内存中读取可能过期的版本

线程A和线程B要进行通信的话,必须要进行以下几个步骤:

  1. 初始化:x = 1,存储在主内存。

  2. 线程A读取:A从主内存读取x,复制x=1到A的工作内存。

  3. 线程B读取:B从主内存读取x,复制x=1到B的工作内存。

  4. 线程A修改:A在工作内存中修改x=2

  5. 线程A写回:A将工作内存中的x=2写回主内存。

  6. 线程B重新读取:B从主内存读取最新的x=2,保证了数据的可见性。

这个过程展示了JMM如何确保多线程环境下的数据一致性

看起来这个流程没什么问题,但是线程A的修改和写回的操作不是原子性的,可能在线程A还未写回,线程B已经重新读取了,这个问题先按下不表


因此:volatile写-读的内存语义

  • 当写一个volatile变量时,JMM会把该线程对应的本地内存中的变量值刷新到主内存。

  • 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程将从主内存中读取共享变量

2)禁止进行指令重排序

volatile关键字禁止指令重排序有两层意思:

  • 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;

  • 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行


🌰例子:以下面的代码来讲述使用和未使用volatile关键字会出现什么杨的情况来理解指令重排

 🔴未使用 volatile 关键字

class Example {
    int a = 0;
    boolean ready = false;

    public void writerThread() {
        a = 5;           // 语句1
        ready = true;    // 语句2
    }

    public void readerThread() {
        while (!ready) { // 语句3
            Thread.yield();
        }
        System.out.println(a); // 语句4
    }
}

编译器和处理器可能会为了优化性能而对指令进行重排序。在这种情况下,语句1和语句2之间没有数据依赖关系,所以它们可能被重排序。

例如:处理器可能会先执行语句2再执行语句1

可能的执行顺序

        写线程:执行语句2,然后执行语句1。

        读线程:执行语句3,检查 ready 是否为 true。如果 ready 已经被设置为 true(即使 a 还没有被设置为5),读线程将进入语句4并打印 a 的值,此时 a 可能还是默认的0。

结果

由于指令重排序,读线程可能在 a 被写线程修改之前就读取了 a 的值,导致输出结果为0,而不是预期的5。这就是指令重排序可能导致的线程安全问题

 🔴使用 volatile 关键字

class Example {
    volatile int a = 0;
    volatile boolean ready = false;

    public void writerThread() {
        a = 5;           // 语句1
        ready = true;    // 语句2
    }

    public void readerThread() {
        while (!ready) { // 语句3
            Thread.yield();
        }
        System.out.println(a); // 语句4
    }
}

指令重排序限制

volatile 关键字会阻止编译器和处理器对与 volatile 变量相关的指令进行重排序。这意味着语句1和语句2之间的执行顺序将被保留,确保了写线程先修改 a 的值,然后再设置 ready 为 true

结果

由于 volatile 的内存屏障效果,读线程在检查 ready 是否为 true 并进入语句4之前,将看到写线程对 a 的最新修改,从而避免了指令重排序引起的线程安全问题


总结来说,volatile 关键字通过提供内存屏障来限制指令重排序,确保了变量的可见性和一定程度上的有序性,从而帮助解决多线程环境下的指令重排序问题

2、 volatile关键字的使用场景

  • 适用场景:volatile适用于那些被多个线程访问但并不涉及复合操作(例如递增操作)的变量。典型的使用场景包括状态标志、控制变量等。

  • 不适用场景:不要将volatile用于需要原子性操作的场景,因为volatile并不能保证原子性。对于需要原子性操作的场景,应该使用锁或者Atomic原子类

实际开发中,几乎看不到volatile的使用,因为volatile只能保证可见性,并不能保证原子性,就需要结合CAS(Compare and Swap)

其实在Java中,java.util.concurrent.atomic包提供了一组原子类,比如AtomicIntegerAtomicLongAtomicBoolean等,它们提供了一种无锁的线程安全机制,以确保对变量的操作是原子性的。

当谈到Atomic原子类的实现原理时,CAS操作是其中的关键。CAS是一种乐观锁技术,它涉及比较内存中的值和预期值,如果相等,则使用新值替换内存中的值。在Java中,CAS是通过Unsafe类实现的,它是一种硬件级别的原子性操作

但是,CAS操作本身无法解决线程可见性的问题,这就是volatile关键字的作用。volatile关键字可以确保变量的写操作立即可见于其他线程,从而解决了线程之间的可见性问题。因此,Atomic原子类是结合了CAS和volatile关键字来实现线程安全

一般情况下都是直接使用的Atomic原子类来保证线程安全的情况,并不会去直接使用volatile关键字

在上面的例子中使用了volatile关键字修改共享变量x的过程,线程A的修改和写回的操作不是原子性的,那么CAS就可以解决这个问题,至于如何解决,在下篇文章再讲吧……

下一篇:Java并发—CAS的原理及应用场景-CSDN博客

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

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

相关文章

使用SpringAOP实现公共字段填充

文章目录 概要整体架构流程技术细节小结 概要 在新增员工或者新增菜品分类时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工或者编辑菜品分类时需要设置修改时间、修改人等字段。这些字段属于公共字段,也就是也就是在我们的系统中很多表…

Flux:Midjourney的新图像模型挑战者

--->更多内容&#xff0c;请移步“鲁班秘笈”&#xff01;&#xff01;<--- Black Forest Labs是一家由前Stability.ai开发人员创立的AI初创公司&#xff0c;旨在为图像和视频创建尖端的生成式 AI 模型。这家初创公司声称&#xff0c;其第一个模型系列Flux.1为文本到图像…

【FAQ】为啥MultipartFile 的InputStream available会为0

背景 在Spring boot 文件上传案例中可能会存在获取MultipartFile InputStream.available()方法为0的情况&#xff0c;导致在文件上传到Minio后对象大小为0的情况 问题原因 在介绍问题原因前我们先探究下MultipartFile 是怎么实现的 这里只是剖析InputStream&#xff0c;所以…

Linux驱动入门实验班day03-另一种注册cdev的方式

问题&#xff1a;原来的函数/*major register_chrdev(0, "100ask_hello", &hello_drv);*/会将主设备号major对应的所有次设备号&#xff0c;对应的设备节点&#xff0c;总是访问到驱动程序hello_drv。 这个问题&#xff0c;会导致主设备号不够用。 解决方式&am…

c++ - unordered_set与unordered_map模拟实现

文章目录 前言一、unordered_set模拟实现二、unordered_map模拟实现 前言 1、unordered_set与unordered_map的介绍与接口使用可参考&#xff1a;unordered_set 、 unordered_map。 2、unordered_set和 unordered_map 的底层实现都是基于哈希表的。哈希表是一种通过哈希函数组织…

HarmonyOS(48) 挂载卸载事件 UI组件的添加和删除监听

UI组件的添加和删除监听 一级目录示例代码参考资料 一级目录 我们通过if条件添加组件的时候&#xff0c;是可以通过onAttach、onDetach、onAppear、onDisAppear来监听组件的添加和删除。 示例代码 // xxx.ets// xxx.ets import { promptAction } from kit.ArkUIEntry Compo…

2024华数杯数学建模A题完整论文讲解(含每一问python代码+结果+可视化图)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了2024 年华数杯全国大学生数学建模竞赛A题机器臂关节角路径的优化设计完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成…

VBA信息获取与处理:VBA代码分类及如何利用代码自动关闭空闲文件

《VBA信息获取与处理》教程(版权10178984)是我推出第六套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。这部教程给大家讲解的内容有&#xff1a;跨应用程序信息获得、随机信息的利用、电子邮件的发送、VBA互…

LC65---2164.对奇偶下标分别排序(排序)--Java版

1.题目 2.思路 &#xff08;1&#xff09;分别提取奇数下标和偶数下标的元素。 &#xff08;2&#xff09;对奇数下标的元素按非递增顺序排序&#xff0c;对偶数下标的元素按非递减顺序排序。 (3)最后将排列好的数字进行合并。 补充&#xff1a; 3.代码实现 class Solution…

PyCharm 2024.1 总结和最新变化

​ 您好&#xff0c;我是程序员小羊&#xff01; 前言 PyCharm 2024.1 是 JetBrains 最新发布的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;旨在提供更强大的功能和更好的用户体验。以下是对这个版本的总结和最新变化的介绍 智能代码建议和自动完成&#xff1a…

C语言 ——— 学习并使用 strerror 函数

目录 学习strerror函数 使用strerror函数 学习strerror函数 库函数在执行的时候&#xff0c;发生了错误&#xff0c;会将这个错误码存放在errno这个变量中&#xff0c;而errno是C语言提供的一个全局变量 而strerror函数是一个错误报告函数&#xff0c;可以将对应的错误码转…

roomformer-端到端矢量检测模型

论文&#xff1a;Connecting the Dots: Floorplan Reconstruction Using Two-Level Queries 论文地址&#xff1a;https://arxiv.org/pdf/2211.15658 code&#xff1a;https://github.com/ywyue/RoomFormer or https://github.com/woodfrog/poly-diffuse 参考&#xff1a;ht…

指针的指针作为形参实测

1. VS2019里面创建C控制台工程 2. 代码 #include <iostream>uint8_t buf[3][10] { {1,2,3},{4,5,6,7,8},{9,0} }; uint8_t len1 3,len2 5,len3 2;void f1(uint8_t **dstBuf, uint8_t *dstLen) {*dstBuf buf[0];*dstLen len1; }void f2(uint8_t** dstBuf, uint8_t*…

密码学基础-数据加密

密码学基础-对称加密与非对称加密 概述 安全通常从四个方面来定义&#xff1a; 机密性完整性合法性&#xff08;可用性&#xff0c;合法的数据才可用&#xff09;不可否认性&#xff08;发送方不可否认发送过的消息&#xff0c;接收方不可否认接收过的消息&#xff09; 对当…

低代码: 开发难点分析,核心技术架构设计

开发难点分析 1 &#xff09;怎样实现组件 核心问题&#xff1a;编辑器 和 页面其实整个就是一系列元素构成的这些元素的自然应该抽象成组件&#xff0c;这些组件的属性应该怎样设计在不同的项目中怎样做到统一的使用 2 &#xff09;跨项目使用 在不同的项目中怎样做到统一的…

最强开源文生图模型一夜易主!SD一作、Stabililty AI核心成员Robin Rombach下场创业了,一出手就是王炸。

时隔4个月&#xff0c;开源文生图模型霸主Stable Diffusion原班人马再创业&#xff01;2024年8月1日官宣&#xff1a;Black Forest Labs成立&#xff0c;公司的第一个产品FLUX.1系列模型包含专业版、开发者版、快速版三种模型&#xff0c;效果直接秒杀Midjourney、DALL-E和Stab…

解决报错:AssertionError: Torch not compiled with CUDA enabled

首先查看自己的cuda是否可用 torch.cuda.is_available()这里我的cuda是不适配torch的&#xff0c;所以需要重新安装适配的torch 查看自己的cuda版本 方法1 方法2 在cmd处输入nvidia-smi 这样可以找到的自己的CUDA版本安装符合自己版本的pytorch 进入pytorch官网https://pyt…

双指针实现删除字符串中的所有相邻重复项

class Solution:def removeDuplicates(self, s: str) -> str:res list(s)slow fast 0length len(res)while fast < length:# 如果一样直接换&#xff0c;不一样会把后面的填在slow的位置res[slow] res[fast]# 如果发现和前一个一样&#xff0c;就退一格指针if slow …

app逆向实战:某监管app2.0.5版本ROOT检测绕过

本篇博客旨在记录学习过程&#xff0c;不可用于商用等其它途径 场景 如下图&#xff0c;在我们打开APP时页面提示如此样式说明被检测到ROOT了&#xff0c;这种情况下无法进入页面请求抓包。 查壳 如果这个APP没有加固&#xff0c;那我们可以通过反编译修改检测的代码或者F…

Django与数据库

目录 创建项目app 路由子表 数据库 创建数据库 什么是ORM 定义数据库表 Django Admin 管理数据 过滤条件 代码直接生成HTML 使用模板 前后端分离架构 对资源的增删改查处理 列出客户 添加客户 临时取消 CSRF 校验 修改客户信息 删除客户 Django中ORM的处理 数据模…