Java并发编程(六):volatile原理详解

Java中的volatile实现原理 volatile依赖lock addl指令在 x86 架构(IA32)的 CPU 上,volatile的写操作通常会被编译成使用lock前缀的指令,如lock addl或lock xchg。这些lock指令确保了写操作的原子性和内存的可见性。lock指令不仅用来实现原子操作,还隐含地提供了完整的内存屏障效果。具体来说,lock指令会强制 CPU 刷新缓存,并禁止其他处理器访问相关的内存地址,从而防止指令重排序。这意味着,lock指令前后的内存操作都不会被重排,因此它可以充当读写屏障。lock。 阅读详情

volatile简介

之前少侠已经介绍过synchronized关键字,volatile也一样是Java中线程同步的重要机制。由JMM内存模型可知,各个线程会将共享变量从主内存中拷贝到工作内存,然后处理器会基于工作内存中的数据进行操作。线程在工作内存进行操作后何时会写到主内存中?这个时机对普通变量是没有规定的,而针对volatile修饰的变量给Java虚拟机特殊的约定,线程对volatile变量的修改会立刻被其他线程所感知,即不会出现数据脏读的现象,从而保证数据的“可见性”。

volatile的初体验

/**
 1. @author Carson
 2. @date 2020/5/29 21:51
 */
public class VolatileService {
    //此时不用volatile修饰
    private static int digit = 0;
    private static boolean isReady = false;
    private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("VolatileService-%d").build();
    private static ExecutorService executorService = new ThreadPoolExecutor(5, 50,1000, TimeUnit.MILLISECONDS,
     new LinkedBlockingQueue<Runnable>(10), threadFactory, new ThreadPoolExecutor.AbortPolicy());
    public static void main(String[] args) {
        executorService.submit(() -> {
            while (true) {
                if (isReady) {
                    System.out.println("Ready");
                    System.out.println(digit);
                    break;
                }
            }
        });
        try {
            Thread.sleep(100L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        digit = 66;
        isReady = true;
    }
}

注意看此时digit和isReady没有用volatile修饰,我利用sleep()方法使得主线程休眠了100毫秒之后继续运行,发现手动开启的线程进入死循环,一直没有输出结果,说明主线程修改的值对另一个线程不可见。然而当我将digit和isReady变量用volatile修饰时,神奇的事情发生了,线程输出了内容并跳出了循环,如下图所示:
在这里插入图片描述
这说明了主线程对volatile修饰的变量值的修改对别的线程是立即可见的。事实上这也验证了大名鼎鼎的happens-before规则:对一个volatile对象的写,happens-before于任意后续对这个volatile域的读。

volatile的实现原理和内存语义

1 实现原理

在生成汇编代码时会在volatile修饰的共享变量进行写操作的时候,JVM会向处理器发出一条Lock前缀的指令,那么Lock前缀的指令在多核处理器下会发现什么事情呢?主要有这两个方面的影响:

  1. 将当前处理器缓存行的数据写回系统内存;
  2. 这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效,进而就会从内存中重读该变量数据,即可以获取当前最新值。

为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

2 内存语义

如下图所示,当volatile变量写后,线程中本地内存中共享变量就会置为失效的状态,因此线程T2再需要读取从主内存中去读取该变量的最新值。下图就展示了线程T2读取同一个volatile变量的内存变化示意图。可以理解为线程T1和线程T2之间进行了一次通信,线程T1在写volatile变量时,实际上就像是给T2发送了一个消息告诉线程T2缓存的是旧值,然后线程T2读这个volatile变量时就只能去主内存拉取了。
在这里插入图片描述
那么JVM中volatile的内存语义是如何实现的呢?答案是内存屏障。JVM内存屏障有四类:
在这里插入图片描述
之前我们有学到指令重排序规则,JVM对volatile变量也指定了相应的规则:
在这里插入图片描述
"NO"表示禁止重排序。为了实现volatile内存语义时,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎是不可能的,为此,JMM采取了保守策略:

  • 在每个volatile写操作的前面插入一个StoreStore屏障,禁止上面的普通写和下面的volatile写重排序;
  • 在每个volatile写操作的后面插入一个StoreLoad屏障,禁止上面的volatile写与下面可能有的volatile读/写重排序;
  • 在每个volatile读操作的后面插入一个LoadLoad屏障,禁止下面所有的普通读操作和上面的volatile读重排序;
  • 在每个volatile读操作的后面插入一个LoadStore屏障,禁止下面所有的普通写操作和上面的volatile读重排序。

需要注意的是:volatile写是在前面和后面分别插入内存屏障,而volatile读操作是在后面插入两个内存屏障。
如下图显示了volatile写和volatile读的指令执行顺序。
在这里插入图片描述
在这里插入图片描述

点点关注,不会迷路

volatile内存语义、原理详解、内存屏障 volatile 阅读详情

相关推荐

Java volatile关键字最全总结:原理剖析与实例讲解(简单易懂)

本文从Java的内存模型和线程的工作原理出发,对volatile关键字进行介绍。帮助读者深入理解volatile的工作机制。同时,还通过简单易懂的示例讲解了如何正确地使用volatile关键字实现线程安全,避免常见的多线程安全问题。

夏日清风 25万+

深度剖析Java的volatile实现原理,再也不怕面试官问了

volatile主要通过汇编lock前缀指令,它会锁定当前内存区域的缓存(缓存行),并且立即将当前缓存行数据写入主内存(耗时非常短),回写主内存的时候会通过MESI协议使其他线程缓存了该变量的地址失效,从而导致其他线程需要重新去主内存中重新读取数据到其工作线程中。当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

Huangjiazhen711的博客 632

Java并发编程(六) : Java内存模型(JMM-原子性、可见性、有序性)、volatile原理、volatile保证可见性, 有序性、单例设计模式(各种实现分析)

共享模型之内存 之前讲的synchronized底层Monitor主要关注的是访问共享变量时,保证临界区代码的 原子性 。下面进一步深入学习共享变量在多线程间的【可见性】问题与多条指令执行时的【有序性】问题 一、 Java 内存模型 JMM 即 Java Memory Model ,它从Java层面定义了主存、工作内存抽象概念,底层对应着CPU 寄存器、缓存、硬件内存、CPU 指令优化等。JMM 体现在以下几个方面 原子性 - 保证指令不会受到线程上下文切换的影响 可见性 - 保证指令不会受 cp

Guizy 2288

volatile 原理

volatile 是 Java 提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。

小鲁蛋的博客 3275

【项目分享】RailTracker: 火车票务数据采集与分析

从12306使用爬虫爬取火车站及车次信息、火车票价 通过本项目,我们将带领访问者手把手完成火车票数据采集、数据清洗、数据集成、可视化的一整个流程!

zhushatong的博客 3345

IsaacLab从入门到精通(二) 导入自定义机器人

IsaacLab系列第二讲,导入自定义的机器人及设置

DejaWu33的博客 8466

【JUC并发编程系列】深入理解Java并发机制:Volatile从底层原理解析到高级应用技巧(六、Volatile关键字、JMM、重排序、双重检验锁)

本文全面解析了Java中Volatile关键字的特性和用法,深入探讨了其在多核CPU架构和Java内存模型(JMM)下的表现。通过介绍JMM的八大同步规范,详细讲解了Volatile的底层实现原理,包括MESI协议的作用机制。同时,分析了Volatile为何不能保证原子性,以及System.out.println()如何确保线程间的数据可见性。此外,还探讨了重排序现象及其对程序的影响,并通过具体案例展示了双重检验锁(DCL)模式下Volatile的重要性,以及如何防御对单例模式的破坏。

欢迎来到我的技术空间!我是一名经验丰富的Java开发工程师,热衷于分享我在软件开发领域的知识和心得。在这个博客里,您会发现一系列关于Java编程的文章,包括最佳实践、技术趋势、代码优化技巧以及一些有趣的项目案例。无论是初学者还是有经验的开发者,都能在这里找 1503

matlab 求解多项式的根 四种方法

matlab 求解多项式的根 四种方法 方法一 %多项式求根函数roots p=[1,-3,1];%建立多项式系数向量p x=roots(p)%求多项式的根 x=-5:0.1:5;%步长为0.1 y1=x.*x-3*x+1;%构建一个函数 y2=zeros(size(x));%产生一个与x同大小的零向量 plot(x,y1,x,y2); 方法二 %利用函数fzero f=@(x)x*x-3*x+1; x1=fzero(f,0.5) x2=fzero(f,2.5) 方法三 %利用f

weixin_57038822的博客 1万+

volatile详解、原理

volatile详解、原理、作用;并发编程;线程可见性、禁止指令重排序;JMM

qq_44700578的博客 1897

第四章 自定义组件、动画

第三章 自定义组件、动画 (一)自定义View 1.1) onMeasure:对当前View的尺寸进行测量 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) widthMeasureSpec和heightMeasureSpec包含测量模式和尺寸大小 int widthMode = MeasureSpec....

Stay Hungry Stay Foolish 求知若饥 虚心若愚 8132

SFF-8654接口引脚定义全解析:从SAS到U.2的高速硬件连接核心

在高速数据存储与传输领域,接口标准是连接主机与存储设备、确保信号完整性与协议互通性的物理基础。其核心原理在于通过精确定义的物理引脚和电气特性,承载不同的高速串行通信协议,如SAS和PCIe。这类高密度连接器的技术价值在于以紧凑的形态支持多协议兼容与高速率传输,满足了企业级存储、高性能计算对带宽和可靠性的苛刻要求。典型的应用场景包括服务器硬盘背板连接、NVMe U.2固态硬盘直连以及外部存储扩展。本文聚焦于SFF-8654(亦称Mini SAS HD)这一关键接口,深入剖析其引脚定义、信号分区(如高速数据通道

weixin_29188043的博客 711

Java并发编程:volatile关键字解析

Java并发编程:volatile关键字解析    volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在Java 5之后,volatile关键字才得以重获生机。   volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情。由于volatile关键字是与Java的内存模

u011396148的博客 1753

30天Python入门(第一天:Python入门)

30天带你入门Python,这是第一天

Science prince的博客 584

JAVA并发编程volatile核心原理

上文说到synchronized,,虽然被评为并发全能王,不过用起来也是格外注意,不能搞大力出奇迹那一套,容易出现性能问题。比如synchronized是无法控制阻塞时长,阻塞不可中断问题;以及锁范围,修饰方法或代码块,要精细,仅修饰需要并发控制部分,降低锁粒度。文末再总结一下,synchronized和volatile的区别,先进入正题,聊聊正主:volatile。volatile是轻量级的并发解决方案,不会阻塞线程,是一种简单的同步机制。

拉丁解牛技术专栏 1462

31.华为路由器:PPPOE配置模拟实验及NAT配置

实验环境 pppoe-client上面的接口信息 [AR1]dis ip interface brief *down: administratively down ^down: standby (l): loopback (s): spoofing The number of interface that is UP in Physical is 2 The number of interface that is DOWN in Physical is 1 The number of interface

迷逝 8117

修改yolov8使支持单通道图像输入

默认支持BGR三通道图像输入,但是在很多工业场景下,存在很多灰度图像的场景,因此对yolov8代码做一些修改以支持单通道图像输入,满足项目实际需求。训练时,只需要在 data 的 yaml 文件中,添加 ch=1 即可。

mingo_敏 5779

【并发编程】详解volatile

简单地说:volatile是“轻量级同步手段”,只解决可见性和有序性,适合变量的“单次读/写”场景;这时候,volatile就能派上用场——它是Java提供的。的操作是“单次写”(线程B)和“单次读”(线程A),没有原子性问题,用volatile保证可见性即可,性能比synchronized高。这是volatile最常用的场景——用一个volatile变量作为“线程间的信号开关”,控制线程的启动、停止或执行逻辑切换。这样一来,所有线程操作的都是主内存的“最新值”,不会出现“一个改了、一个没看见”的情况。

m0_73784704的博客 1071

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

【java总结】并发编程原理回顾

并发编程其实就那些事

zhujohnle的专栏 2万+

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Java并发编程利剑之volatile的应用及原理

Java并发编程中的volatile的应用及原理

mmc173168的博客 993

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Java 并发编程synchornized和volatile的实现原理

目录 1. volatile的定义与实现原理 2.synchronized的实现原理与应用 3.锁的升级与对比 3.1.偏向锁 3.2.轻量级锁 3.3重量级锁 4.锁的优缺点对比 5.volatile跟Synchronize的区别 6.参考文章 在并发编程过程中 经常要处理一些:可见性 、 原子性 、 有序性的问题。 “本文主要介绍synchronized和volatile到实现原理”synchronized和volatile几乎是java面试基础部分必会,不会你就吃...

纸上得来终觉浅,绝知此事要躬行 869

Java 并发编程:synchronized、volatile

Java 并发编程:核心理论 并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能。它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰、思维缜密,这样才能写出高效、安全、可靠的多线程并发程序。本系列会从线程间协调的方式(wait、notify、notifyAll)、Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制。在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式、实现源

song_myth的博客 1542

Java 并发编程CAS、volatile、synchronized原理详解

CAS(CompareAndSwap) What–CAS 在Java中调用的是Unsafe的如下方法来CAS修改对象int属性的值: /** * * @param o 对象所在类本身的对象(一般这里是对一个对象的属性做修改,才会出现并发) * @param offset 属性在对象中的相对偏移量位置(获取偏移量也是通过unsafe的⼀个⽅法: objectFieldOffset(Fieldfield)来获取属性在对象中的偏移量;) * @param exp.

July的博客 2910

Java 并发编程:volatile的使用及其原理

Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁、偏向锁) Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) Java 并发编程:volatile的使用及其原理 一、volatile的作用   在《Java并发编程:核心理论》一文中,我们已经提到过可见性、有序性及原子性问题,...

绝圣弃智-零的博客 326

Java并发编程之volatile和synchronized原理

Java并发编程之volatile和synchronized原理

csdn15917204572的博客 833

Java 并发编程五:volatile的使用及其原理

Java 并发编程:volatile的使用及其原理 Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁、偏向锁) Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) Java 并发编程:volatile的使用及其原理 一...

zhuiqiuuuu的博客 232

Java并发编程系列 | volatile关键字的底层原理

volatile关键字volatile的介绍volatile的底层实现原理JSR(java规范)内存屏障的介绍缓存行的介绍通过缓存行对齐对使用volatile优化的一个案例使用volatile来保证禁止指令重排序的场景,单例模式怎么才能验证真的会有代码的重排序呢 volatile的介绍 volatile可以用于在变量前面进行修饰,而被它修饰了的变量有两个作用: 保证该变量对所有线程的可见性,所以使用该变量时的值是一定是最新的 禁止指令重排序 volatile的底层实现原理 其底层的实现依然使用到了汇编层

Liu_G的博客 420
上一篇: 深入理解MySQL中的事务【超详细配图版】
下一篇: 【EasyExcel】后台开发如何利用EasyExcel优雅的操作Excel
少侠露飞
博客等级 码龄9年 214粉丝 117原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值