news 2026/3/12 6:59:48

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/3/10 8:31:36

ECSHOP电商平台深度解析:从零搭建专业网店的完整攻略

ECSHOP电商平台深度解析:从零搭建专业网店的完整攻略 【免费下载链接】ecshop ECShop是一款开源免费的网上商店系统 项目地址: https://gitcode.com/gh_mirrors/ec/ecshop 在当今数字化时代,拥有一个专业的电商平台是企业成功的关键。ECSHOP电商平…

作者头像 李华
网站建设 2026/3/10 9:02:44

AMD显卡AI图像生成革命:ComfyUI-Zluda智能加速方案深度解析

AMD显卡AI图像生成革命:ComfyUI-Zluda智能加速方案深度解析 【免费下载链接】ComfyUI-Zluda The most powerful and modular stable diffusion GUI, api and backend with a graph/nodes interface. Now ZLUDA enhanced for better AMD GPU performance. 项目地址…

作者头像 李华
网站建设 2026/3/11 18:29:05

SysML v2实战指南:从系统建模新手到高效工程师的进阶之路

SysML v2实战指南:从系统建模新手到高效工程师的进阶之路 【免费下载链接】SysML-v2-Release The latest incremental release of SysML v2. Start here. 项目地址: https://gitcode.com/gh_mirrors/sy/SysML-v2-Release 作为一名在系统建模领域摸爬滚打多年…

作者头像 李华
网站建设 2026/3/10 18:19:24

STM32CubeMX下HSE到SYSCLK路径详解

从HSE到SYSCLK:STM32时钟路径的实战解析 你有没有遇到过这样的情况?代码烧进去,板子一上电,系统却卡在启动阶段不动了——既不跑main函数,也不进中断。查来查去,最后发现是 时钟没起来 。 在STM32的世界…

作者头像 李华
网站建设 2026/3/12 20:20:45

紧急警告:云端AI数据正在泄露!立即切换Open-AutoGLM本地模式

第一章:紧急警告:云端AI数据正在泄露!立即切换Open-AutoGLM本地模式近期多起安全事件表明,使用云端AI服务处理敏感数据时,用户输入内容可能被记录、分析甚至用于模型再训练。其中,Open-AutoGLM 云端接口已被…

作者头像 李华
网站建设 2026/3/11 18:26:09

杰理之设置蓝牙加密的【篇】

////设置蓝牙加密的level //io_capabilities ; /*0: Display only 1: Display YesNo 2: KeyboardOnly 3: NoInputNoOutput*/ //authentication_requirements: 0:not protect 1 :protect __set_simple_pair_param(1, 0, 1);

作者头像 李华