李锋镝的博客

  • 首页
  • 时间轴
  • 说说
  • 系列文章
  • AI工具集
  • 论坛
  • 左邻右舍
    • 左邻右舍
    • 朋友圈
    • 游客中心
  • 留言
    • 留言
    • 走心评论
  • 关于
    • 关于本站
    • Now
    • 每日心情
    • 网站地图
    • 网站统计
    • 赞助
  • 星球穿梭
  • 🚇开往
白雪成红
惟坚韧者始能遂其志
  1. 首页
  2. 原创
  3. 正文

JAVA关键字之volatile关键字说明

2019年10月11日 约 1,721 字6 分钟 192 1 0
本文最后更新于 2019年10月11日,距今已 2556 天,其中的信息可能已经发生变化,请注意甄别。

volatile作用

volatile主要是为了解决多线程内存不可见问题。

  • 对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。

其次是为了保证代码的有序性。

虚拟机在编译的时候,是有可能把代码的顺序进行重排序的,不一定会按照我们写的代码的顺序来执行,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题的。

  • 如果一个变量被声明volatile的话,那么这个变量不会被进行重排序,也就是说,虚拟机会保证这个变量之前的代码一定会比它先执行,而之后的代码一定会比它慢执行。

总结一下volatile作用主要就是两点:

  1. 可见性
  2. 有序性

内存不可见原因

  1. cache机制导致内存不可见
    • CPU的运行速度是远远高于内存的读写速度的,为了不让cpu为了等待读写内存数据,现代cpu和内存之间都存在一个高速缓存cache(实际上是一个多级寄存器)。线程在运行的过程中会把主内存的数据拷贝一份到线程内部cache中,也就是working memory。这个时候多个线程访问同一个变量,其实就是访问自己的内部cache。
  2. 除了cache的原因,重排序后的指令在多线程执行时也有可能导致内存不可见,由于指令顺序的调整,线程A读取某个变量的时候线程B可能还没有进行写入操作呢,虽然代码顺序上写操作是在前面的。

volatile原理

当一个变量被声明为volatile时,在编译成会变指令的时候,会多出下面一行:

0x00bbacde: lock add1 $0x0,(%esp);

这句指令的意思就是在寄存器执行一个加0的空操作。不过这条指令的前面有一个lock(锁)前缀。
当处理器在处理拥有lock前缀的指令时:
在之前的处理中,lock会导致传输数据的总线被锁定,其他处理器都不能访问总线,从而保证处理lock指令的处理器能够独享操作数据所在的内存区域,而不会被其他处理所干扰。
但由于总线被锁住,其他处理器都会被堵住,从而影响了多处理器的执行效率。为了解决这个问题,在后来的处理器中,处理器遇到lock指令时不会再锁住总线,而是会检查数据所在的内存区域,如果该数据是在处理器的内部缓存中,则会锁定此缓存区域,处理完后把缓存写回到主存中,并且会利用缓存一致性协议来保证其他处理器中的缓存数据的一致性。

缓存一致性协议

线程中的处理器会一直在总线上嗅探其内部缓存中的内存地址在其他处理器的操作情况,一旦嗅探到某处处理器打算修改其内存地址中的值,而该内存地址刚好也在自己的内部缓存中,那么处理器就会强制让自己对该缓存地址的无效。所以当该处理器要访问该数据的时候,由于发现自己缓存的数据无效了,就会去主存中访问。

使用volatile需要注意的

volatile不能完全保证一个变量的线程安全。因为Java里面的运算并非是原子操作,所以导致volatile声明的变量无法保证线程安全。

所谓原子操作:

一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。也就是说,处理器要么把这组操作全部执行完,中间不允许被其他操作所打断,要么这组操作不要执行。

其实大部分情况下volatile还是可以保证线程安全的问题的,前提是满足以下条件:

  1. 对变量的写操作不依赖于当前值。
  2. 该变量没有包含在具有其他变量的不变式中。

第一个条件的限制使volatile变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而volatile不能提供必须的原子特性。实现正确的操作需要使x的值在操作期间保持不变,而volatile变量无法实现这点。(然而,如果将值调整为只从单个线程写入,那么可以忽略第一个条件。)

再来解释下第二个条件,如下代码,它包含了一个不变式:下界总是小于或等于上界。

// 非线程安全的数值范围类
@NotThreadSafe 
public class NumberRange {
    private int lower, upper;

    public int getLower() { return lower; }
    public int getUpper() { return upper; }

    public void setLower(int value) { 
        if (value > upper) 
            throw new IllegalArgumentException(...);
        lower = value;
    }

    public void setUpper(int value) { 
        if (value < lower) 
            throw new IllegalArgumentException(...);
        upper = value;
    }
}

上面的代码中,如果将lower、upper定义为volatile类型,并不能充分达到预期的线程安全的效果。假如凑巧两个线程在同一时间使用不一致的值执行setLower和setUpper的话,则会使范围处于不一致的状态。如果初始状态是 (0, 5)(lower = 0, upper = 5),同一时间内,线程 A 调用 setLower(4) 并且线程 B 调用setUpper(3),显然这两个操作交叉存入的值是不符合条件的,那么两个线程都会通过用于保护不变式的检查,使得最后的范围值是 (4, 3) —— 一个无效值。至于针对范围的其他操作,我们需要使 setLower() 和 setUpper() 操作原子化 —— 而将字段定义为 volatile 类型是无法实现这一目的的。

关于如何正确使用volatile关键字,可以看下这片文章:volatile变量使用指南,关于volatile的使用讲解的非常清晰。

参考链接

1
  1. 1volatile变量使用指南www.ibm.com
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可
分享到

JAVA关键字之volatile关键字说明

也可使用浏览器菜单中的「分享」功能

微信扫一扫分享

标签: JAVA JAVA关键字 多线程 线程安全 锁
最后更新:2019年10月11日

岁月同一天 10 月 11 日

回望过去的今天,你在写什么

  • 中间件
    1 年前 2025年10月11日
    Kafka 为什么要抛弃 Zookeeper?

    Kafka 抛弃 Zookeeper(引入 KRaft 模式)是为了解决长期依赖 Zookeeper 带来的性能瓶颈、架…

  • 中间件
    1 年前 2025年10月11日
    高性能场景为什么推荐使用PostgreSQL,而非MySQL?

    在高性能场景(如高并发读写、复杂查询、大数据量存储、多维度数据处理等)中,PostgreSQL 常被优先推荐,核心原因在…

  • 原创
    7 年前 2019年10月11日
    JAVA关键字之volatile关键字说明

    volatile作用 volatile主要是为了解决多线程内存不可见问题。 对于一写多读,是可以解决变量同步问题,但是如…

相关文章
  • 数据库中的锁-SQL Server版2019年10月9日
  • SpringBoot启动概述(SpringBoot2.1.7)2020年1月10日
  • 跨平台版本管理神器,开发者的环境配置救星:vfox2025年10月27日
  • JDK25模块级导入深度解析:Java导入机制的革命性进化2026年1月26日
  • JWT 实现登录认证 + Token 自动续期方案2025年4月7日

李锋镝

既然选择了远方,便只顾风雨兼程。

打赏 点赞
< 上一篇
下一篇 >
1234567891112131415161718192021222324252627282930313233343536373839404142434446474849505152535455575859606162636465666769727476777879808182858687909293949596979899
取消回复
…

文章评论

还没有评论,快来抢沙发吧~

天若有情天亦老,人间正道是沧桑。

听点儿音乐吧 朋友~
文章目录
最新 热点 随机
最新 热点 随机
讨论下XXL-JOB GLUE模式的安全问题 心气没了,干什么都不得劲 Kratos-plus v1.1.26版本更新说明 我觉得是时候公布我的导航网站了 代码是 AI 写的,生产事故谁背锅? 给主题增加了游客中心功能
给主题增加了游客中心功能我觉得是时候公布我的导航网站了游本昌去世关于主题加载速度优化的一点儿小演进给主题增加了Now、每日心情、年度回顾、岁月同一天、随机漫步等功能心气没了,干什么都不得劲
Hibernate、MyBatis的简介、区别以及优化 【PostgreSQL 15】PostgreSQL 15对UNIQUE和NULL的改进 分布式架构知识体系(超全核心知识大图) 如何通过命令查看Java应用内存中对象数量 使用OpenShift搭建k8s集群 使用WireGuard在Ubuntu 24.04系统搭建VPN
最近评论
老张博客 发布于 15 小时前(10月10日) NAS现在就不要搞了吧,除非你手里有内存和硬盘。
李锋镝 发布于 20 小时前(10月10日) 确实,很多不必要的东西,买了回来也是吃灰……
林羽凡 发布于 23 小时前(10月10日) 一个是不想折腾了,另一个有些觉得没必要的折腾或购买,想想就会算了。
李锋镝 发布于 24 小时前(10月10日) 少年老成吧大概是 :21:
Hary 发布于 24 小时前(10月10日) 我也是这样,感觉都不想折腾了,我还没到中年呢吧啊啊啊
标签聚合
WordPress MySQL Redis Claude SpringBoot 数据库 Theme Spring IDEA AI编程 AI 分布式 K8s SQL 日常 JAVA ElasticSearch JVM 架构 多线程
友情链接
  • 老张博客
  • 哥斯拉
  • 林羽凡
  • lijie blog
  • 皮皮社
  • 瓦匠个人小站
  • 搬砖日记
  • Serendipity
  • 九仞之行
  • 彬红茶日记
  • sssr7844的博客
  • Honesty
  • 懋和道人
  • 志文工作室
  • 韩小韩博客
  • 韩情脉脉
  • Mr.Sun的博客
  • 知向前端

COPYRIGHT © 2016-2026 lifengdi.com. ALL RIGHTS RESERVED.

lifengdi.com 十年之约虫洞

Domain age badge for lifengdi.com

Theme Kratos-plus By Dylan Li

津ICP备2024022503号-3

京公网安备11011502039375号