java基础—Volatile关键字详解

news2024/11/25 15:39:38

java基础—Volatile关键字详解

文章目录

  • java基础—Volatile关键字详解
      • 并发编程的三大特性:
      • volatile的作用是什么
      • volatile如何保证有可见性
        • volatile保证可见性在JMM层面原理
        • volatile保证可见性在CPU层面原理
        • 可见性问题的例子
      • volatile如何保证有序性
        • 单例模式使用volatile保证有序性的例子
      • volatile为什么不能保证原子性

并发编程的三大特性:

原子性、可见性和有序性。只要有一条原则没有被保证,就有可能会导致程序运行不正确。volatile关键字 被用来保证可见性,即保证共享变量的内存可见性以解决缓存一致性问题。一旦一个共享变量被 volatile关键字 修饰,那么就具备了两层语义:内存可见性和禁止进行指令重排序。

  • 原子性:就是一个操作或多个操作中,要么全部执行,要么全部不执行。

    例如:账户A向账户B转账1000元,这个么过程涉及到两个操作,(1)A账户减去1000元 (2)B账户增加1000元。这么两个操作必须具备原子性。否则A账户钱少了,B账户没增加。

  • 有序性: 程序执行顺序按照代码先后顺序执行。

    处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致(指令重排),但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。(此处的结果一致指的是在单线程情况下)

    指令重排的理解:单线程侠,如果两个操作更换位置后,对后续操作结果没有影响,可以对这两个操作可以互换顺序。

  • 可见性: 可见性是指多线程共享一个变量,其中一个线程改变了变量值,其他线程能够立即看到修改的值。

    //线程1执行的代码
    int i = 0;
    i = 10;
    //线程2执行的代码
    j = i;
    
    

    CPU1执行线程1代码,CPU执行线程2代码。CPU读取i=0到CPU缓存中,修改i=10到自己缓存,还没更新到主存,此时CPU2读取的i还是主存中i=0,此时j会被赋值为0;

volatile的作用是什么

volatile是一个类型修饰符,JDK1.5之后,对其语义进行了增强。

  • 保证了不同线程之间对共享变量操作的可见性
  • 通过禁止编译器、CPU指令重排序和部分hapens-before规则,解决有序性

volatile如何保证有可见性


volatile保证可见性在JMM层面原理

volatile修饰的共享变量在执行写操作后,会立即刷回到主存,以供其它线程读取到最新的记录。

volatile保证可见性在CPU层面原理

volatile关键字底层通过lock前缀指令,进行缓存一致性的缓存锁定方案,通过总线嗅探和MESI协议来保证多核缓存的一致性问题,保证多个线程读取到最新内容。 lock前缀指令除了具有缓存锁定这样的原子操作,它还具有类似内存屏障的功能,能够保证指令重排的问题。

  • 被volatile修饰的变量在写操作生成汇编指令时,会多出Lock前缀指令,这个指令会引起CPU缓存刷回主存。
  • 刷回主存后,导致其他核心缓存了该内存地址的数据无效,通过缓存一致性协议(MESI)保证每个线程的数据是最新的。
  • 缓存一致性协议保证每个CPU核心通过嗅探在总线上传播的数据来检查自己的缓存是不是被修改,· 当 CPU 发现自己缓存行对应的内存地址被修改,会将当前 CPU 的缓存行设置成无效状态,重新从内存中把数据读到 CPU 缓存

可见性问题的例子

启动线程1和线程2,线程2设置stop=true。查看线程1是否会停止

public class TestVisibility {

    //是否停止 变量
    private static boolean stop = false;
    public static void main(String[] args) throws InterruptedException {


        new Thread(() -> {
            System.out.println("线程 1 正在运行...");
            while (!stop) ;
            System.out.println("线程 1 终止");
        }).start();

        //休眠 10 毫秒
        Thread.sleep(10);
        //启动线程 2, 设置 stop = true
        new Thread(() -> {
            System.out.println("线程 2 正在运行...");
            stop = true;
            System.out.println("设置 stop 变量为 true.");
        }).start();
    }
    
}

可见,线程1并不会停止,而是一直循环下去。这就是CPU缓存导致的一致性问题。

给stop加上volatile关键字,并运行,会发现线程1终止了

volatile如何保证有序性

  1. 内存屏障(Memory Barrier 又称内存栅栏,是一个 CPU 指令)禁止重排序

    Volatile关键字(JMM内存屏障),内存屏障也成为内存栏杆,是一个CPU指令,volatile修饰的变量,在读写操作前后都会进行屏障的插入来保证执行的顺序不被编译器等优化器锁重排序。

    内存屏障的功能有两个:(1)阻止屏障两边的指令重排、(2)刷新处理器缓存(保证内存可见性)image-20230204181902182

  2. 3 个 happens-before 规则实现:

    Happens-Before
    SR-133 提出了 happens-before 的概念,通过这个概念来阐述操作之间的内存可见性。如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在 happens-before 关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。 与程序员密切相关的 happens-before 规则如下:

    • 程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
    • 监视器锁规则: 对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
    • volatile 变量规则: 对一个 volatile 域的写,happens- before 于任意后续对这个 volatile 域的读。
    • 传递性: 如果 A happens- before B,且 B happens- before C,那么 A happens- before C。

    注意,两个操作之间具有 happens-before 关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before 仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前(the first is visible to and ordered before the second)。happens- before 的定义很微妙

    img

单例模式使用volatile保证有序性的例子

为什么变量singleton之前需要加volatile

public class Singleton {
    public static volatile Singleton singleton;

    /**
     * 构造函数私有,禁止外部实例化
     */
    private Singleton() {};

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤:

  • 分配内存空间。
  • 初始化对象。
  • 将内存空间的地址赋值给对应的引用。

但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:

  • 分配内存空间。
  • 将内存空间的地址赋值给对应的引用。
  • 初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量

volatile为什么不能保证原子性

执行下面代码会发现,输出的并不是10000

public class atomiciVolitile {

    volatile  int  i = 0;
    public void addI(){
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        atomiciVolitile a=new atomiciVolitile();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(10);//执行速度太快,没有起到并发作用,等待10毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                a.addI();
            }).start();
        }

        Thread.sleep(5000);

        System.out.println(a.i);

    }
}

image-20230208194634572

原因:i++其实是一个复合操作,包括三步骤:

  • 读取i的值。
  • 对i加1。
  • 将i的值写回内存。

volatile是无法保证这三个操作是具有原子性的,我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。

想要了解更详细,请看这篇

java基础—java内存模型(JMM)CPU架构、缓存一致性、重排序、JMM的实现、JMM保证可见性、有序性问题的详解

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

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

相关文章

概率统计·假设检验【正态总体均值的假设检验、正态总体方差的假设检验】

均值假设检验定义 2类错误 第1类错误&#xff08;弃真&#xff09;&#xff1a;当原假设H0为真&#xff0c;观察值却落入拒绝域&#xff0c;因而拒 绝H0这类错误是“以真为假” 犯第一类错误的概率显著性水平α第2类错误&#xff08;取伪&#xff09;&#xff1a;当原假设H0不…

基于参数化模型的3D产品配置器开发

当我被要求为客户创建3D产品配置器时&#xff0c;我想到的第一个平台是 SketchFab。 我是在澳大利亚墨尔本的 SAE 创意媒体学院学习计算机动画时接触到它的。 推荐&#xff1a;使用 NSDT场景设计器 快速搭建 3D场景。 1、基于Sketchfab开发3D产品配置器 SketchFab 是在线共享…

码住!为什么一定要做tiktok小店?赶快来开启爆单模式!

随着短视频和社交电商的结合&#xff0c;商家的变现渠道变得更加宽广。各大平台也开启了直播带货的赛道&#xff0c;在跨境行业中&#xff0c;tiktok小店也是商家们关注的渠道。很多商家也开始做tiktok小店&#xff0c;那我们可以先了解一下这小店的优势。tiktok小店拥有优质的…

并发线程、锁、ThreadLocal

并发编程并发编程Java内存模型&#xff08;JMM&#xff09;并发编程核心问题—可见性、原子性、有序性volatile关键字原子性原子类CAS(Compare-And-Swap 比较并交换)ABA问题Java中的锁乐观锁和悲观锁可重入锁读写锁分段锁自旋锁共享锁/独占锁公平锁/非公平锁偏向锁/轻量级锁/重…

02- pandas 数据库 (机器学习)

pandas 数据库重点: pandas 的主要数据结构: Series (一维数据)与 DataFrame (二维数据)。 pd.DataFrame(data np.random.randint(0,151,size (5,3)), # 生成pandas数据 index [Danial,Brandon,softpo,Ella,Cindy], # 行索引 …

J6412四网口迷你主机折腾虚拟机教程

今天给大家做一个四网口迷你主机折腾虚拟机的安装教程&#xff0c;主机采用的是maxtang大唐NUC J6412 intel i226V四网口的迷你主机&#xff0c;这款主机它是不能直接装上NAS的&#xff0c;必须使用虚拟机系统&#xff0c;近期研究了下然后做了一个教程分享给大家。 首先需要做…

Antd-table全选踩坑记录

目录 一、需求 二、问题 ​编辑三、解决 四、全选选中所有数据而不是当前页 一、需求 最近遇到一个小小的需求&#xff0c;在我们这个项目中&#xff0c;有一个表格需要添加全选删除功能。这还不简单吗&#xff0c;于是我找到andt的官网&#xff0c;咔咔咔一顿cv&#xff0…

「自控元件及线路」14 电子电力技术与功率放大器概述

本节介绍电子电力技术的基本概念 本节介绍PD、SCR、GTR、MOSFET、IGBT等电子电力器件 本节介绍功率放大器的基本概念和线性功率放大器 文章目录电力电子技术概述电能变换电子电力器件功率二极管PD晶闸管SCR功率晶体管GTR功率场效应晶体管PowerMOSFET绝缘栅双极晶体管IGBT功率放…

代码随想录.力扣.二叉树.105/106. 从中序与前序/后序序列构造二叉树

题目&#xff1a; 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9,3,15,20,7] 输出: [3,9,2…

Notion 笔记Mac及windows客户端汉化

1、 注册/登录账号&#xff1a; https://www.notion.so/zh-cn 2、下载电脑软件应用&#xff1a; https://www.notion.so/desktop 下载完成后&#xff0c;安装到电脑中&#xff0c;界面如下&#xff1a; 3、下载汉化js插件 地址&#xff1a;https://github.com/Reamd7/n…

String、StringBuffer、StringBuilder有什么区别?

第5讲 | String、StringBuffer、StringBuilder有什么区别&#xff1f; 今天我会聊聊日常使用的字符串&#xff0c;别看它似乎很简单&#xff0c;但其实字符串几乎在所有编程语言里都是个特殊的存在&#xff0c;因为不管是数量还是体积&#xff0c;字符串都是大多数应用中的重要…

【PR】源窗口

【PR】源窗口源窗口粗剪源窗口按钮功能标记按钮出入点相关插入与覆盖插入覆盖导出帧使用软件&#xff1a;Premiere2020 源窗口粗剪 我们手上可能有一些很长的视频&#xff0c;但是我就想要其中的几段&#xff0c;这个时候粗剪就很方便&#xff0c;把想要的视频段落剪出来先凑一…

私募证券基金动态-23年1月报

成交量&#xff1a;1月日均7,901.31亿元2023年1月A股两市日均成交7,901.31亿元&#xff0c;环比上升0.33%、同比下降25.18%。1月恰逢春节仅16个交易日&#xff0c;节后2个交易日交易量明显回暖。管理人&#xff1a;新提交备案51家&#xff0c;备案通过21家1月新提交备案申请的5…

分析了 200 个 DeFi 项目,我发现了这些规律

作者&#xff1a;Ren & Heinrich翻译&#xff1a;dongdong在这篇文章中&#xff0c;我分享了我通过分析当前排名前 200 的 DeFi 加密项目的见解。这不是一项学术研究。尽管如此&#xff0c;这些发现对加密货币投资者来说具有附加值。我使用 https://defillama.com/ 的公共数…

财报解读:业务复苏迹象明显,中国中免能否重写增长神话?

2月3日&#xff0c;中国中免披露2022年度业绩快报&#xff0c;2022年总营收为544.63亿元&#xff0c;同比下降19.52%&#xff1b;实现归属于上市公司股东的净利润50.25亿元&#xff0c;同比下降47.95%。来源&#xff1a;中国中免2022年度业绩快报业绩近乎腰斩&#xff0c;但从长…

库存管理系统软件哪个好用 盘点前十名!

库存管理怎么做&#xff1f;库存管理系统有什么用处&#xff1f;市面上那么多库存管理系统&#xff0c;我们又如何挑选呢&#xff1f; 本文将为您介绍库存管理系统挑选的方法以及库存管理系统的选型。 库存管理系统前十名为&#xff1a;1、简道云库存管理软件&#xff1b;2、…

网络通讯的理解

tcp/ip 协议族ip在真实环境中&#xff0c;会把主机号再分成一个子网号和一个主机号。这样的主机号才是最终容纳的主机数量。所以需要使用子网掩码&#xff08;32位&#xff09;来分子网号和主机号。其中值为1的比特是网络号和子网号&#xff0c;值为0的是比特是主机号。可以在w…

真的麻了,别再为难软件测试员了......

前言 有不少技术友在测试群里讨论&#xff0c;近期的面试越来越难了&#xff0c;要背的八股文越来越多了,考察得越来越细&#xff0c;越来越底层&#xff0c;明摆着就是想让我们徒手造航母嘛&#xff01;实在是太为难我们这些测试工程师了。 这不&#xff0c;为了帮大家节约时…

Nacos【一】Nacos集群部署配置

系列文章目录 暂无 文章目录系列文章目录前言一、Nacos集群架构1.ip直连2. SLB3. 域名-SLB二、集群部署准备2.1 机器准备2.2 Nginx安装配置1.安装2.负载均衡配置2.3 nacos安装配置1.nacos节点2. MySQL准备1.Docker安装MySQL2. nacos对应数据库初始化三、 集群启动1.失败原因汇…

Python深度学习实战PyQt5信号与槽的连接

本文讲解信号与槽的连接机制&#xff0c;详细示范各种类型的信号/槽连接的实现方法&#xff0c;这是图形用户界面的核心内容。还将介绍面向对象的程序设计&#xff0c;这是图形用户界面的基本思想目录1. 信号与槽&#xff08;Signals and slots&#xff09;信号与槽机制是 PyQt…