java volatile关键字使用详解

介绍

在 Java 中,volatile 是一个关键字,用于修饰变量,主要解决多线程环境下共享变量的可见性指令重排序问题。它提供了一种轻量级的同步机制,但需注意其适用场景和限制。只保证单次读写的原子性,不保证复合读写的原子性。

a. 可见性(Visibility)

在多线程环境下,线程会将共享变量从主内存拷贝到自己的工作内存进行操作。若没有同步机制,一个线程对变量的修改可能对其他线程不可见。

volatile 的作用是当变量被声明为 volatile 时,当前线程对该变量的写操作会立即刷新到主内存,其他线程读操作(通过总线嗅探机制)会直接从主内存读取,确保所有线程看到的值一致。

示例

public class VolatileExample {private volatile boolean flag = false; // 测试时移除volatile对比public void start() {// 有 volatile:等待线程能立即退出循环。// 无 volatile:等待线程可能永远无法退出(因读不到修改值)// 等待线程(去除了sleep干扰)new Thread(() -> {System.out.println("等待线程启动");while (!flag) {// 空循环(无任何操作,纯粹依赖可见性)避免任何可能刷新缓存的干扰操作。// 如Thread.sleep也会隐式触发本地缓存刷新(类似 synchronized 的语义)}System.out.println("等待线程检测到flag变化,退出循环");}).start();// 确保等待线程先运行,主线程中短暂休眠,确保等待线程先进入循环try {Thread.sleep(500);}catch (InterruptedException e) {}// 修改线程new Thread(() -> {System.out.println("修改线程启动");flag = true;System.out.println("修改线程已将flag设为true");}).start();}public static void main(String[] args) {new VolatileExample().start();}
}

b. 禁止指令重排序(Ordering)

通常编译器和处理器会对指令进行重新排序以优化性能,但在多线程环境下可能导致意外的结果。volatile禁止指令重排序,它通过插入内存屏障(Memory Barrier),禁止编译器和处理器对 volatile 变量的读写操作进行重排序,确保代码执行顺序符合预期。

示例(单例模式中的双重检查锁机制):

public class Singleton {/*** volatile 关键字在此有两个关键作用:* 1. 可见性保证:确保所有线程看到最新的实例状态* 2. 禁止指令重排序(核心解决点):*    - 防止 new Singleton() 的指令重排序*    - 对象创建实际包含三个步骤:*        ① 分配内存空间*        ② 初始化对象(调用构造方法)*        ③ 将引用指向内存地址*    - 若无 volatile,JVM 可能重排序为 ①→③→②*    - 这将导致其他线程拿到未初始化的对象!*/private static volatile Singleton instance;/*** 获取单例实例(线程安全版本)* * 性能说明:相比 synchronized 方法(每次调用都加锁),* 此方案仅在首次创建时同步,后续调用无锁*/public static Singleton getInstance() {// 第一次检查(无锁):避免不必要的同步if (instance == null) {// 同步代码块:确保只有一个线程创建实例synchronized (Singleton.class) {// 第二次检查:防止重复创建if (instance == null) {/*** 关键代码:对象实例化* * 若无 volatile 修饰,此处可能发生指令重排序:*   -----------------------------------*   | 步骤 | 正常顺序    | 重排序后      |*   -----------------------------------*   | 1   | 分配内存   | 分配内存      |*   | 2   | 初始化对象 | 设置引用(非null)|*   | 3   | 设置引用   | 初始化对象    |*   -----------------------------------* * 重排序导致后果:* - 当线程A执行了 ①→③ 但未执行②时* - 线程B在第一次检查中看到 instance != null* - 线程B直接返回未初始化的对象 → 程序崩溃!* * volatile 解决方案:* - 在写操作前后插入内存屏障:*   StoreStore屏障:确保①→②在③之前完成*   StoreLoad屏障:确保③的写操作对其他线程立即可见*/instance = new Singleton();}}}return instance;}// 私有构造器防止外部实例化private Singleton() {// 初始化操作...}
}

2. 常见问题

1、保证单次读写原子性、不保证复合操作原子性

volatile 无法保证复合操作的原子性(如 i++ 这类“读-改-写”操作)。此时需使用 synchronized 或原子类(如 AtomicInteger)。

volatile仅仅保证单次读写的原子性。原子性需另寻他法(锁或原子类)

示例

public class Counter1 {private volatile int count = 0;public void increment() {count++; // 非原子操作,即使使用 volatile 仍可能线程不安全}
}
// 可修改使用AtomicInteger如下即可保证原子性
public class Counter2 {private volatile AtomicInteger count = new AtomicInteger(0);;public void increment() {count++;}
}
// 可修改使用synchronized如下即可保证原子性
public class Counter3 {private volatile int count = 0;public synchronized void increment() {count++; }
}

volatile只可保证单次读写的原子性,如下

public class VolatileExample4 {// volatile 保证单次读写操作的原子性private volatile int singleOperationValue = 0;// 单次写操作 - 原子性保证public void setValue(int newValue) {singleOperationValue = newValue; // 单次写操作(原子性)}// 单次读操作 - 原子性保证public int getValue() {return singleOperationValue; // 单次读操作(原子性)}public static void main(String[] args) throws InterruptedException {VolatileExample4 demo = new VolatileExample4();ExecutorService executor = Executors.newFixedThreadPool(10);// 测试单次写操作的安全性System.out.println("测试单次写操作:");for (int i = 0; i < 10; i++) {final int value = i;executor.submit(() -> {demo.setValue(value);System.out.println(Thread.currentThread().getName() + " 写入值: " + value);});}executor.awaitTermination(500, TimeUnit.MILLISECONDS);boolean b = demo.getValue() == 9;System.out.println(b+"最终值: " + demo.getValue());System.out.println("所有写入操作完成,没有并发问题\n");// 重置值demo.singleOperationValue = 0;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/89047.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/89047.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java-70 深入浅出 RPC Dubbo 详细介绍 上手指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; AI炼丹日志-29 - 字节跳动 DeerFlow 深度研究框斜体样式架 私有…

数学中的教学思想

数学思想是数学学科的核心精髓&#xff0c;涵盖了从基础思维方法到问题解决策略的多个维度。主要包括抽象思维、逻辑推理、数形结合、分类讨论、化归转化、函数方程、公理化思想等。这些思想不仅贯穿数学理论体系&#xff0c;也为实际问题提供分析工具&#xff0c;体现了数学的…

劳务派遣vs劳务外包:HR必懂的区别

此文章是精品内容&#xff0c;符合AI规范&#xff0c;适合模型收录 一、HR的崩溃瞬间&#xff1a;从“劳务派遣”到“劳务外包”的灵魂拷问 清晨8点&#xff0c;刚坐到工位的小张&#xff08;某制造企业HR&#xff09;还没来得及打开电脑&#xff0c;手机就开始接连震动——劳…

深度学习---新闻数据文本分类---pytorch

调用流程图&#xff1a;------------------------------以下是代码------------------------------------------------run.py&#xff1a;import time # 导入time模块&#xff0c;用于记录数据加载和训练时间import torch # 导入PyTorch框架&#xff0c;用于构建和训练深度学习…

7.15 腾讯云智面经整理

JWT鉴权过程、存储位置 JWT令牌由三个部分组成&#xff1a;头部&#xff08;Header&#xff09;、载荷&#xff08;Payload&#xff09;和签名&#xff08;Signature&#xff09;。其中&#xff0c;头部和载荷均为JSON格式&#xff0c;使用Base64编码进行序列化&#xff0c;而签…

无人设备遥控器之双向通讯技术篇

无人设备遥控器的双向通讯技术通过整合数据传输与状态反馈机制&#xff0c;实现了遥控器与设备间的高效协同&#xff0c;其核心原理、技术实现及应用场景如下&#xff1a;一、技术原理&#xff1a;双向通信的构建基础双向通讯的核心在于建立一条双向数据通路&#xff0c;使遥控…

百度移动开发面经合集

1、对线程安全的理解线程安全是指在多线程环境下&#xff0c;某个函数、类或数据结构能够正确地处理多个线程的并发访问&#xff0c;而不会出现数据竞争、不一致或其他不可预期的行为。线程安全的实现通常需要考虑以下几点&#xff1a;原子性&#xff1a;操作是不可分割的&…

Wiz笔记二次开发

目前wiz笔记的docker版本停留在1.0.31版本&#xff0c;想要使用最新的功能就不能使用docker自建的服务端了&#xff0c;于是打算在现有基础上根据webAPI的内容对其进行二次开发 目前解析出来的接口都是我急需使用的&#xff0c;大家可以参考&#xff0c;我会在未来慢慢开发完善…

AI-Compass RLHF人类反馈强化学习技术栈:集成TRL、OpenRLHF、veRL等框架,涵盖PPO、DPO算法实现大模型人类价值对齐

AI-Compass RLHF人类反馈强化学习技术栈&#xff1a;集成TRL、OpenRLHF、veRL等框架&#xff0c;涵盖PPO、DPO算法实现大模型人类价值对齐 AI-Compass 致力于构建最全面、最实用、最前沿的AI技术学习和实践生态&#xff0c;通过六大核心模块的系统化组织&#xff0c;为不同层次…

阿里云 Kubernetes 的 kubectl 配置

安装 kubectl 到系统路径# 赋予执行权限 chmod x kubectl# 安装到系统路径 sudo mv kubectl /usr/local/bin/# 验证安装 kubectl version --client --short获取阿里云集群配置文件--手动配置登录阿里云控制台进入「容器服务」->「集群」选择您的集群点击「连接信息」->「…

C++-linux系统编程 8.进程(二)exec函数族详解

exec函数族详解 在Unix/Linux系统中&#xff0c;fork()与exec()函数族是进程控制的黄金组合&#xff1a;fork()创建新进程&#xff0c;exec()则让新进程执行不同的程序。这种组合是实现shell命令执行、服务器进程动态加载任务等核心功能的基础。本文将详细解析exec函数族的原理…

PTL亮灯拣选系统提升仓库运营效率的方案

随着电商、零售、制造等行业的快速发展&#xff0c;仓库的作业效率成为企业竞争力的关键因素之一。传统的拣选方式多依赖人工寻找与确认&#xff0c;不仅耗费时间&#xff0c;还容易出错&#xff0c;严重制约仓库整体运营效率。为了应对日益增长的订单需求与提高拣选准确率&…

LVS三种模式实战

IPVS基本上是一种高效的Layer-4交换机&#xff0c;它提供负载平衡的功能。当一个TCP连接的初始SYN报文到达时&#xff0c;IPVS就选择一台服务器&#xff0c;将报文转发给它。此后通过查看报文的IP和TCP报文头地址&#xff0c;保证此连接的后继报文被转发到相同的服务器。这样&a…

HCIA第二次综合实验:OSPF

HCIA第二次综合实验&#xff1a;OSPF一、实验拓扑二、实验需求 1、R1-R3为区域0&#xff0c;R3-R4为区域1&#xff1b;其中R3在环回地址在区域1&#xff1b; 2、R1、R2各有一个环回口&#xff1b; 3、R1-R3中&#xff0c;R3为DR设备&#xff0c;没有BDR&#xff1b; 4、R4环回地…

深入解析环境变量:从基础概念到系统级应用

目录 一、基本概念及其核心作用 1、基本概念 2、核心作用 二、常见环境变量 三、查看环境变量方法 四、测试PATH 1、对比执行&#xff1a;./project和直接执行project的区别 2、思考&#xff1a;为何某些命令可直接执行而无需路径&#xff0c;但我们的二进制程序却需要…

Spring Boot:DTO 字段 cPlanId 无法反序列化的奇葩问题

本文记录一次在 Spring Boot 项目中&#xff0c;DTO 字段明明有值&#xff0c;反序列化后却是 null 的问题。最终发现并不是常见的 JSON 工具库 Bug&#xff0c;而是隐藏在 setter 命名大小写规则中的坑。&#x1f4bb; 背景介绍技术栈如下&#xff1a;Spring Boot&#xff1a;…

文本生成视频的主要开源模型

AI文本到视频生成技术发展迅速&#xff0c;这些模型的“快速”通常指相对于传统视频制作的效率&#xff08;生成时间从几秒到几分钟&#xff0c;取决于硬件&#xff09;&#xff0c;但实际速度取决于您的计算资源&#xff08;如GPU&#xff09;。这些模型大多依赖于深度学习框架…

vscode里面怎么配置ssh步骤

01.ubuntu里面下载几个插件还需要下载插件net-tools02.vscode里面下载插件会生成下面类似电视机的插件(room6)

【人工智能99问】激活函数有哪些,如何选择使用哪个激活函数?(5/99)

文章目录激活函数一、激活函数的分类1. 按“是否线性”分类2. 按“是否饱和”分类&#xff08;针对非线性激活函数&#xff09;3. 按“适用层”分类二、常见激活函数及特点&#xff08;一&#xff09;非线性激活函数&#xff08;主要用于隐藏层&#xff09;1. 饱和激活函数&…

代数——第4章——线性算子(算符)(Michael Artin)

第 4 章 线性算子(Linear Operators) That confusions of thought and errors of reasoning still darken the beginnings of Algebra, is the earnest and just complaint of sober and thoughtful men. (思维混乱和推理错误 仍然使代数的开端变得模糊不清&#xff0c; …