news 2026/6/13 21:34:47

Java中volatile的介绍

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java中volatile的介绍


一、volatile的特性

当声明共享变量为volatile后,对这个变量的读/写将会很特别。理解volatile特性的一个好方法是把对volatile变量的单个读/写(不是类似val++的复合操作),看成是使用同一个锁对这些单个读/写操作做了同步。下面通过具体的示例来说明。

class VolatileFeaturesExample { volatile long vl = 0L; // 使用volatile定义一个64位的long型的共享变量v1 public void set(long l) { vl = l; // 单个volatile变量的写 } public void getAndIncrement() { vl++; // 复合(多个)volatile变量的读/写,存在竞态条件,线程不安全,需要加锁 } public long get() { return vl; // 单个volatile变量的读 } }

假设有多个线程分别调用上面程序的3个方法,这个程序在语义上和下面程序等价。

class VolatileFeaturesExample1 { long vl = 0L; // 定义一个64位的long型普通变量 public synchronized void set(long l) {// 对单个的普通变量的写用同一个锁同步,锁对象为this vl = l; } public void getAndIncrement() { // 普通方法调用 long temp = get(); // 调用已同步的读方法 temp += 1L; // 普通写操作,存在竞态条件,线程不安全,需要加锁 set(temp); // 调用已同步的写方法 } public synchronized long get() { // 对单个的普通变量的读用同一个锁同步,锁对象为this return vl; } }

如上面示例程序所示,一个volatile变量的单个读/写操作,与一个普通变量的读/写操作都是使用同一个锁来同步,它们之间的执行效果相同。

注意:上面的getAndIncrement()方法是非线程安全的,因为其中存在竞态条件(value++实际上包含三个独立的操作:1 读取value;2 将 value加1;3 将计算结果写入value)

锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性,这意味着对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。锁的语义决定了临界区代码的执行具有原子性。这意味着,即使是64位的long型和double型变量,只要它是volatile变量,对该变量的读/写就具有原子性(单个volatile变量)。如果是多个volatile操作或类似于volatile++这种复合操作整体上不具有原子性。

volatile变量自身具有下列特性

可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。

原子性:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。

二、volatile写-读建立的happens-before关系

从内存语义的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果:volatile写和锁的释放有相同的内存语义;volatile读与锁的获取有相同的内存语义。请看下面使用volatile变量的示例代码。

class VolatileExample { int a = 0; volatile boolean flag = false; public void writer() { a = 1; // 1 flag = true; // 2 } public void reader() { if (flag) { // 3 int i = a; // 4 System.out.println("i=" + i); } } }

假设线程A执行writer()方法之后,线程B执行reader()方法。根据happens-before规则,这个过程建立的happens-before关系可以分为3类:

1)根据程序次序规则 1 happens-before 2;3 happens-before 4。

2)根据volatile规则 2 happens-before 3。

3)根据happens-before的传递性规则 1 happens-before 4。

三、volatile写-读的内存语义

volatile写的内存语义如下,当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存

以上面示例程序VolatileExample为例,假设线程A首先执行writer()方法,随后线程B执行reader()方法,初始时两个线程的本地内存中的flag和a都是初始状态。图3-17是线程A执行volatile写后,共享变量的状态示意图。

如图所示线程A在写flag变量后,本地内存A中被线程A更新过的两个共享变量的值被刷新到主内存中。此时,本地内存A和主内存中的共享变量的值是一致的。

四、volatile读的内存语义如下

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

如图所示,在读flag变量后,本地内存B包含的值已经被置为无效。此时,线程B必须从主内存中读取共享变量。线程B的读取操作将导致本地内存B与主内存中的共享变量的值变成一致。

如果我们把volatile写和volatile读两个步骤综合起来看的话,在读线程B读一个volatile变量后,写线程A在写这个volatile变量之前所有可见的共享变量的值都将立即变得对读线程B可见。

下面对volatile写和volatile读的内存语义做个总结。(重点理解下面的总结)

>线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。

>线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息。

>线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/12 5:01:44

Open-AutoGLM模型下载全攻略:从注册到运行一步到位

第一章:智谱开源Open-AutoGLM模型在哪獲取 智谱AI推出的Open-AutoGLM是一款面向自动化图学习任务的开源模型,旨在降低图神经网络的应用门槛。该模型支持自动图结构构建、特征工程与模型选择,适用于金融风控、知识图谱、推荐系统等复杂场景。 …

作者头像 李华
网站建设 2026/5/28 16:52:18

如何评估Dify平台在实际业务中的ROI表现?

如何评估Dify平台在实际业务中的ROI表现? 在企业纷纷拥抱AI的今天,一个现实问题摆在面前:我们投入了不菲的成本接入大模型,为什么产出却迟迟不见起色?开发周期长、效果不稳定、维护成本高——这些痛点让不少AI项目最终…

作者头像 李华
网站建设 2026/6/9 17:19:39

Windows文件格式转换器:高效右键菜单解决方案

Windows文件格式转换器:高效右键菜单解决方案 【免费下载链接】FileConverter File Converter is a very simple tool which allows you to convert and compress one or several file(s) using the context menu in windows explorer. 项目地址: https://gitcode…

作者头像 李华
网站建设 2026/5/28 16:52:19

LeetDown:让A6/A7设备降级变简单的macOS神器

LeetDown:让A6/A7设备降级变简单的macOS神器 【免费下载链接】LeetDown a GUI macOS Downgrade Tool for A6 and A7 iDevices 项目地址: https://gitcode.com/gh_mirrors/le/LeetDown 还在为iPhone 5s、iPad 4等老设备无法降级而烦恼吗?LeetDown这…

作者头像 李华
网站建设 2026/5/29 2:29:39

为什么顶尖AI团队都在关注Open-AutoGLM的开源?真相令人震惊

第一章:Open-AutoGLM模型开源的行业震动 Open-AutoGLM的正式开源在人工智能领域引发强烈反响,其基于高效推理架构与开放训练框架的设计理念,迅速吸引了全球开发者与研究机构的关注。该模型不仅支持多模态任务处理,还通过模块化设…

作者头像 李华
网站建设 2026/6/11 19:47:33

项目应用中因toolchain配置不当引发c9511e的复盘总结

一次c9511e错误引发的深度复盘:当编译器找不到自己的家在某个寻常的工作日早晨,CI 流水线突然挂了。构建日志里只有一行刺眼的红字:error: c9511e: unable to determine the current toolkit check that arm_tool_v6 is set correctly and po…

作者头像 李华