爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      volatile与JMM

      首页 知识中心 其他 文章详情页

      volatile与JMM

      2023-06-26 08:26:00 阅读次数:426

      JMM,volatile

      被volatile修饰的变量有两大特点

      可见性

      写完后 立即刷新回主内存并及时发出通知,大家可以去主内存拿最新版,前面的修改对后面所有线程可见

      有序性

      不存在数据依赖关系,可以重排序
      存在数据依赖关系,禁止重排序

      但重排后的指令绝对不能改变原有的串行语义!这点在并发设计中必须要重点考虑!


      当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值 立即刷新回主内存中
      当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,重新会到主内存中读取最新共享变量
      所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取

      volatile为什么可以保证可见性和有序性

      内存屏障 Memory Barrier

      内存屏障(也成为内存栅栏,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会 要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些你内存屏障指令,volatile实现了Java内存模型中的可见性和有序性(禁重排), 但volatile无法保证原子性。

      内存屏障之前 的所有 写操作 都要回写到主内存
      内存屏障之后 的所有 读操作 都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)


      写屏障(Store Memory Barrier) :告诉处理器在写屏障之前将所有的存储在缓存(store bufferes)中的数据同步到主内存。也就是说当看到Store屏障指令,就必须把该指令之前所有写入指令执行完毕后才能继续往下执行。

      读屏障(Load Memory Barrier) :处理器在读屏障之后的读操作,都在读屏障之后执行。也就是说在Load屏障指令之后就能够保证后面的读取数据指令一定能够读取到最新的数据。

      volatile与JMM

      因此重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前。一句话:对一个volatile变量的写,先行发生于任意后续对这个volatile变量的读,也叫写后读

      粗分为2种

      读屏障(Load Barrier):在读指令之前插入读屏障,让工作内存或CPU高速缓存当中的缓存数据失效,重新回到主内存中获取最新数据

      写屏障(Store Barrier):在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中

      第一个操作 第二个操作:普通读写 第二个操作:volatile读 第二个操作:volatile写
      普通读写 可以重排 可以重排 不可以重排
      volatitle读 不可以重排 不可以重排 不可以重排
      volatitle写 可以重排 不可以重排 不可以重排

      当第一个操作为volatile读时,不论第二个操作是什么,都不能重排序。这个操作保证了volatile 读之后的操作不会被重排到volatile读之前。
      当第二个操作为volatile写时,不论第一个操作是什么,都不能重排序。这个操作保证了volatile 写之前的操作不会被重排到volatile写之后。
      当第一个操作为volatile写时,第二个操作为volatile读时,不能重排


      读屏障:在每个volatile读操作的后面插入一个LoadLoad屏障,在每个读操作的后面插入一个LoadStore屏障

      volatile与JMM

      写屏障:在每个volatile写操作的后面插入一个StoreStore屏障,在每个写倒错的后面插入一个StroeLoad屏障

      volatile与JMM

      JMM将内存屏障插入策略分为4中规则

      volatile与JMM

      volatile特性

      说明:保证不同线程对某个变量完成操作后结果及时可见,即该共享变量一旦改变所有线程立即可见。

      保证可见性

      示例

        //static boolean flag = true;
          static volatile boolean flag = true;
      
          public static void main(String[] args)
          {
              new Thread(() -> {
                  System.out.println(Thread.currentThread().getName()+"\t -----come in");
                  while(flag)
                  {
      
                  }
                  System.out.println(Thread.currentThread().getName()+"\t -----flag被设置为false,程序停止");
              },"t1").start();
      
              //暂停几秒钟线程
              try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
      
              flag = false;
      
              System.out.println(Thread.currentThread().getName()+"\t 修改完成flag: "+flag);
      
          }
      

      上述代码:不加volatile,没有可见性,程序无法停止

      ​ 加了volatile,保证可见性,程序可以停止

      程序无法停止的问题可能:

      1. 主线程修改了flag之后没有将其刷新到主内存,所以t1线程看不到
      2. 主线程将flag刷新到了主内存,但是t1一直读取的是自己工作内存中flag的值,没有去主内存中更新获取flag最新的值(自己工作内存是每个线程私有的,主内存是共享内存)

      想得到的结果:

      1. 线程中修改了自己工作内存中的副本之后,立即将其刷新到主内存
      2. 工作内存中每次读取工作变量时,都去主内存中重新读取,然后拷贝到工作内存

      解决:

      使用volatile修饰共享变量,就可以达到上面的效果,被volatile修改的变量有以下特点:

      1. 线程中读取的时候,每次读取都会去主内存中读取共享变量最新的值,然后将其复制到工作内存
      2. 线程中修改了工作内存中变量的副本,修改之后会立即刷新到主内存

      Java内存模型中定义的8种每个线程自己的工作内存 与主物理内存之间的原子操作

      read(读取)→load(加载)→use(使用)→assign(赋值)→stroe(存储)→write(写入)→lock(锁定)→unlock(解锁)

      volatile与JMM

      read: 作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存
      load:作用于工作内存,将read从主内存传输的变量值放入工作内存变量副本中,即数据加载
      use:作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作
      assign:作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到一个给变量赋值字节码指令时会执行该操作
      store:作用于工作内存,将赋值完毕的工作变量的值写回给主内存
      write:作用于主内存,将store传输过来的变量值赋值给主内存中的变量

      由于上述6条只能保证单条指令的原子性,针对多条指令的组合性原子保证,没有大面积加锁,所以,JVM提供了另外两个原子指令

      lock:作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程

      unlock:作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用

      没有原子性

      class MyNumber
      {
          volatile int number;
      
          public void addPlusPlus()  //synchronized
          {
              number++;
          }
      }
      
      public class VolatileNoAtomicDemo
      {
          public static void main(String[] args)
          {
              MyNumber myNumber = new MyNumber();
      
              for (int i = 1; i <=10; i++) {
                  new Thread(() -> {
                      for (int j = 1; j <=1000; j++) {
                          myNumber.addPlusPlus();
                      }
                  },String.valueOf(i)).start();
              }
      
              //暂停几秒钟线程
              try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
      
              System.out.println(myNumber.number);  //正确结果应该为:10000,实际输出却不是
      
          }
      }
      

      volatile与JMM

      不加锁的情况下,当线程1对主内存对象发起read操作到write操作第一套流程的时间里,线程2随时都有可能对这个主内存对象发起第二套操作

      volatile与JMM

      volatile与JMM

      对于volatile变量具备可见性,JVM只是保证从主内存加载到线程工作内存的值是新的,也仅是数据加载时是新的。但是多线程环境下,”数据计算“ 和 ”数据赋值“ 操作可能多次出现,若数据在加载之后,若主内存volatile修饰变量发生修改之后,线程工作内存中的操作将会作废去读主内存最新值(本身具备可见性),操作出现写丢失问题,即各线程私有内存和主内存公共内存中变量不同步,进而导致数据不一致。由此可见volatile解决的是变量读时的可见性问题,但无法保证原子性,对于多线程修改主内存共享变量的场景必须使用加锁同步。

      原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。

      public void add(){
          i++;  //不具备原子性,该操作是先读取值,然后写回一个新值,相当于原来的值加上1,分3步完成
      }
      

      如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,那么第二个线程就会与第一个线程一起看到同一个值,并执行相同值的加1操作,这也就造成了线程安全问题,因此对于add方法必须使用synchronzed修饰以便保证线程安全。

      volatile变量不适合参与到依赖当前值的运算

      指令禁重排

      重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序,不存在数据依赖关系,可以重排序

      存在数据依赖关系,禁止重排序

      但重拍后的指令绝对不能改变原有的串行语义!这点在并发设计中必须要重点考虑!

      重排序的分类和执行流程

      volatile与JMM

      数据依赖性:若两个操作访问同一变量,且这两个操作中有一个为写操作,此时两操作间就存在数据依赖性。

      重排前 重排后
      int a = 1; //第一句
      int b = 2; //第二句
      int c = a + b; //3
      int b = 2; //第一句
      int a = 1; //第二句
      int c = a + b; //3
      结论:假设编译器调整了语句的顺序,但是不影响程序的最终结果。 重排序OK

      下面的示例:若存在数据依赖关系,禁止重排序。重排序发生后,会导致程序运行结果不同

      - 代码 说明
      写后读 a = 1;
      b = a;
      写一个变量之后,再读这个变量
      写后写 a = 1;
      a = 2;
      写一个变量之后,再写这个变量
      读后写 a = b;
      b = 1;
      读一个变量之后,再写这个变量

      volatile正确使用

      1. 单一赋值可以,但是含复合运算赋值不可以(i++之类)

        // 单一赋值
        volatile int a = 10;
        volatile boolean flag = false;
        
      2. 状态标志:判断业务是否结束

      private volatile static boolean flag = true;
      
          public static void main(String[] args) {
              new Thread(()->{
                  while (flag){
                      //do something
                  }
              },"t1").start();;
              try {Thread.sleep(2L);} catch (InterruptedException e) {throw new RuntimeException(e);}
              
              new Thread(()->{
                  flag = false;
              },"t2").start();;
          }
      
      1. 开销较低的读,写锁策略

        /**
         * 使用:当读远多于写,结合使用内部锁和volatile变量来减少同步的开销
         * 理由:利用volatile保证读取操作的可见性,利用synchronized保证复合操作的原子性
        */
        public class Counter{
                private volatile int value;
        
                public int getValue(){
                    return value; //利用volatile保证读取操作的可见性
                }
                public synchronized int intcrement(){
                    return value++; //利用synchronized保证复合操作的原子性
                }
            }
        
      2. DCL双端锁的发布(double-checked-locking)

        多线程下的单例模式

      volatile与JMM

      volatile总结

      1)volatile可见性
      2)volatile没有原子性
      3)volatile禁重排
      4)volatile关键字系统底层加入内存屏障,两者是如何关联的
      5)内存屏障是什么
      6)内存屏障作用
      7)内存屏障四大指令

      3句话总结

      1)volatile写之前的操作,都禁止重排序到volatile之后
      2)volatile读之后的操作,都禁止重排序到volatile之前
      3)volatile写之后volatile读,禁止重排序

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.51cto.com/u_14452299/6017146,作者:鱼找水需要时间,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:IDEA一个项目启动多个实例,以集群启动

      下一篇:用大白话解释什么是Socket

      相关文章

      2025-03-18 09:59:32

      Java中的内存模型与并发控制:从基础到高级

      Java中的内存模型与并发控制:从基础到高级

      2025-03-18 09:59:32
      Java , JMM , 内存 , 并发 , 死锁 , 线程 , 编程
      2025-02-19 09:04:11

      经典面试题-volatile的作用

      经典面试题-volatile的作用

      2025-02-19 09:04:11
      volatile , 优化 , 共享 , 变量 , 指令 , 线程
      2024-12-27 08:03:29

      java 如何再volatile内部调用接口

      在Java中,volatile 关键字通常用于确保变量的可见性和有序性,而不是用来修饰接口或方法调用的。volatile 修饰的变量会被立即同步到主存,并且在每次访问时都会从主存中重新读取,而不是从缓存中读取。

      2024-12-27 08:03:29
      Java , volatile , 变量 , 接口 , 线程 , 调用
      2024-12-13 06:53:39

      java基础-线程间通信方式

      在 Java 中,线程间的通信是非常重要的,尤其是在多线程编程中,它有助于协调线程的行为,确保资源的正确访问和更新。

      2024-12-13 06:53:39
      Java , util , volatile , 实现 , 示例 , 线程
      2024-06-11 09:32:51

      并发编程系列教程(05) - Java内存模型

      ​共享内存模型​ 指的就是Java内存模型(简称​​JMM​​),​JMM​决定一个线程对共享变量的写入时,能对另一个线程可见。

      2024-06-11 09:32:51
      JMM , 内存 , 线程
      2024-05-29 09:01:43

      全面理解JMM模型

      Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,是一种规范,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

      2024-05-29 09:01:43
      JMM , 内存
      2024-04-18 09:42:00

      Java内存模型(JMM)及其工作原理,包括栈、堆、方法区等各部分的功能

      Java内存模型(JMM)及其工作原理,包括栈、堆、方法区等各部分的功能Java内存模型(JMM)是Java虚拟机(JVM)定义的一种抽象概念,用于描述计算机内存如何被Java程序使用和访问的规范。

      2024-04-18 09:42:00
      JMM , 内存 , 线程
      2024-04-16 08:57:13

      读volatile文章的自我理解

      读volatile文章的自我理解

      2024-04-16 08:57:13
      java , volatile
      2024-04-16 02:44:32

      volatile可以保证原子性吗

      volatile可以保证原子性吗

      2024-04-16 02:44:32
      volatile , 线程
      2023-07-19 08:12:05

      JMM内存模型

      JMM内存模型

      2023-07-19 08:12:05
      JMM , 内存 , 线程
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5264265

      查看更多

      最新文章

      读volatile文章的自我理解

      2024-04-16 08:57:13

      volatile的个人理解

      2023-04-27 08:01:30

      查看更多

      热门文章

      volatile的个人理解

      2023-04-27 08:01:30

      读volatile文章的自我理解

      2024-04-16 08:57:13

      查看更多

      热门标签

      linux java python javascript 数组 前端 docker Linux vue 函数 shell git 节点 容器 示例
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      读volatile文章的自我理解

      volatile的个人理解

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号