JUC并发编程之volatile详解
目录
1. volatile
1.1 volatile关键字的作用
1.1.1 变量可见性
1.1.2 禁止指令重排序
1.2 volatile可见性案例
1.3 volatile非原子性案例
1.4 volatile 禁止重排序
1.5 volatile 日常使用场景
送书活动
1. volatile
在并发编程中,多线程操作共享的变量时,可能会导致线程安全问题,如数据竞争、可见性问题等。为了解决这些问题,Java提供了JUC(java.util.concurrent)工具包,其中包含了很多用于处理并发编程的工具类和接口。在JUC中,volatile是一个关键字,它可以用于修饰变量,用来确保变量的可见性和禁止指令重排序,从而在一定程度上解决线程安全问题。
1.1 volatile关键字的作用
1.1.1 变量可见性
在多线程环境下,如果一个线程修改了共享变量的值,其他线程可能由于线程间的数据不一致性而看不到该变量的最新值。这种问题称为“变量不可见性”或“可见性问题”。
volatile关键字可以确保被修饰的变量对所有线程可见。当一个线程修改了volatile变量的值,其他线程立即能够看到修改后的最新值,而不会使用缓存中的旧值。
1.1.2 禁止指令重排序
在JVM(Java虚拟机)中,为了优化性能,编译器和处理器可能会对指令进行重排序。在单线程环境下,这种重排序不会影响程序的执行结果。然而,在多线程环境下,指令重排序可能会导致线程安全问题。
volatile关键字可以防止指令重排序,确保被修饰的变量按照代码中的顺序执行。
1.2 volatile可见性案例
public class VolatileExample {private static volatile boolean flag = false;public static void main(String[] args) {new Thread(() -> {while (!flag) {System.out.println("Waiting for the flag to be true...");}System.out.println("Flag is now true. Exiting the thread.");}).start();try {Thread.sleep(1000); // 确保主线程在子线程之前执行} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Setting the flag to true...");flag = true;}
}
在上面的示例中,我们创建了一个VolatileExample类,并声明了一个volatile类型的flag变量。在主线程中,我们启动一个新的子线程,该子线程会不断地检查flag变量的值,直到flag变为true时,子线程退出。
在主线程中,我们将flag变量设置为true。由于flag变量被声明为volatile类型,子线程能够及时看到flag的最新值,从而退出循环,输出“Flag is now true. Exiting the thread.”。
这个示例演示了volatile关键字的作用,确保了flag变量的可见性。如果我们没有使用volatile关键字,子线程可能会一直循环下去,因为它看不到主线程对flag的修改。

1.3 volatile非原子性案例
public class Test {public static void main(String[] args) throws InterruptedException {VolatileAtomicityExample example = new VolatileAtomicityExample();for(int i=1;i<=100;i++){new Thread(()->{for(int j=1;j<=1000;j++)example.increment();},String.valueOf(i)).start();}TimeUnit.SECONDS.sleep(2);System.out.println(example.getCount());}}
class VolatileAtomicityExample {volatile int count = 0;public void increment() {count++;}public int getCount() {return count;}
}
我们创建了一个VolatileAtomicityExample类,其中的成员变量count被声明为volatile类型。然后,我们创建了100个线程,每个线程分别执行1000次increment()操作,对count进行自增。最后,我们在主线程中打印count的最终值。以上示例中的输出结果可能会因为运行时的不确定性而有所不同。每次运行时可能得到不同的结果,但通常结果都小于100000。为了解决这个问题,我们需要使用synchronized关键字或其他线程安全机制来确保increment()方法的原子性。
这是什么原因呢?

对于volatile变量具备可见性,JVM只是保证从主内存加载到线程工作内存的值是最新的,也仅是数据加载时是最新的。但是多线程环境下,"数据计算"和"数据赋值"操作可能多次出现,若数据在加载之后,若主内存volatile修饰变量发生修改之后,线程工作内存中的操作将会作废去读主内存最新值,操作出现写丢失问题。即各线程私有内存和主内存公共内存中变量不同步,进而导致数据不一致。由此可见volatile解决的是变量读时的可见性问题,但无法保证原子性,对于多线程修改主内存共享变量的场景必须使用加锁同步。
1.4 volatile 禁止重排序
内存屏障是一种硬件指令或者编译器指令,用于控制内存操作的顺序,以确保多线程环境下的内存可见性和正确的执行顺序。
内存屏障分为两种类型:
-
内存读屏障(Load Barrier):它是一个特殊的硬件或者编译器指令,用于保证在内存读取操作之前,所有的先行写操作都已经完成,并且其结果对当前线程可见。也就是说,读屏障可以防止后续读取指令重排序到读屏障之前的位置。
-
内存写屏障(Store Barrier):它是一个特殊的硬件或者编译器指令,用于保证在内存写入操作之前,所有的先行写操作和写屏障之前的写操作都已经完成,并且其结果对其他线程可见。也就是说,写屏障可以防止前面的写入指令重排序到写屏障之后的位置。
volatile关键字通过内存屏障来保证变量的读写操作不会被重排序。具体来说,对于volatile变量的写操作,在写入变量之后会插入写屏障,这样可以防止其他指令重排序到写屏障之前。类似地,对于volatile变量的读操作,在读取变量之前会插入读屏障,这样可以防止其他指令重排序到读屏障之前。
通过这种方式,volatile关键字确保了对变量的读写操作具有一定的有序性,从而保证了多线程环境下的内存可见性和正确的执行顺序。
1.5 volatile 日常使用场景
状态标志:当一个线程修改了某个状态标志,其他线程需要立即看到最新的状态。这时可以使用volatile关键字修饰状态标志,保证其在多线程之间的可见性。例如:
public class Task implements Runnable {private volatile boolean isRunning = true;@Overridepublic void run() {while (isRunning) {// 执行任务逻辑}}public void stop() {isRunning = false;}
}
双重检查锁定(Double-Checked Locking):在多线程环境下,当需要延迟初始化一个对象时,为了避免重复初始化,常常使用双重检查锁定。在这种情况下,需要使用volatile关键字来确保对象在多线程环境中的可见性。例如:
public class Singleton {private volatile static Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
在上述代码中,我们实现了一个简单的单例模式。在getInstance()方法中,我们使用双重检查锁定来实现延迟初始化,确保只有在instance为空时才创建新的Singleton实例。
然而,在多线程环境下,由于指令重排序的存在,可能会导致以下问题:
-
对象引用不为空但尚未初始化:在线程A执行完
instance = new Singleton();这一行之前,可能发生指令重排序,导致instance的引用不为空,但是Singleton实例的初始化还未完成。这样,线程B在执行return instance;时就会得到一个尚未初始化的对象,导致错误。 -
可见性问题:指令重排序也可能导致线程B无法及时看到线程A的初始化操作。例如,线程A对
instance的赋值可能被重排到线程A的后面执行,从而线程B在读取instance时得到一个旧的引用,无法感知线程A的初始化操作。
为了解决这个问题,需要在创建Singleton实例时使用volatile关键字来保证对象的可见性和禁止指令重排序。
送书活动
《硅基物语·我是灵魂画手》
当AI遇上绘画,会打开怎样的奇妙世界?
用ChatGPT+Midjourney西出人类的灵魂与梦想
用StableDiffusion+D-ID画出青春绚丽的渴望
激活每个人隐藏的绘画天赋
人人都能成为顶尖绘画大师
ChatGPT+Midjourncy+StableDiffusion+D-ID+RunwayGen-l
爆火软件全流程协作
掌据AI绘商技巧
解锁超全绘画关键司
讲解创作底层逻辑
一本书讲透AI绘画全流程
内容简介
一本将AI绘画讲透的探秘指南,通过丰富的实践案例操作,通俗易懂地讲述AI绘画的生成步骤生动展现了AI绘画的魔法魅力。从历史到未来,跨越百年时空,从理论到实践,讲述案例操作:从技术到哲学,穿越多个维度,从语言到绘画,落地实战演练。AI绘画的诞生,引发了奇点降临,点亮了AGI(通用人工智能),并涉及 Prompt、风格,技术细节、多模态交互,AIGC等一系列详细讲解。让您轻松掌握生图技巧,创造出独特的艺术作品,书写属于自己的艺术时代。

当当网链接:http://product.dangdang.com/29601870.html
关注博主、点赞、收藏、
评论区评论
即可参与送书活动!
相关文章:
JUC并发编程之volatile详解
目录 1. volatile 1.1 volatile关键字的作用 1.1.1 变量可见性 1.1.2 禁止指令重排序 1.2 volatile可见性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1.5 volatile 日常使用场景 送书活动 1. volatile 在并发编程中,多线程操作共享的变量时&a…...
swing布局详解
1. 布局管理器接口 (1)说明 布局管理器接口为LayoutManager和LayoutManager2,LayoutManager2是LayoutManager的子类。 (2)常用方法 方法描述LayoutManageraddLayoutComponent(String name, Component comp) removeL…...
el-table某一列嵌套使用el-popover,使用click触发,导致页面下拉框组件无法触发弹框关闭(解决办法)
在弹框触发的方法里加上document.body.click() 即可 尝试了很多其他的方法都没用,只有这个解决了 完整代码: <el-select change"sourceChange" clearable ><el-optionv-for"option in list1":key"option.code":…...
正泰电力携手图扑:VR 变电站事故追忆反演
VR(Virtual Reality,虚拟现实)技术作为近年来快速发展的一项新技术,具有广泛的应用前景,支持融合人工智能、机器学习、大数据等技术,实现更加智能化、个性化的应用。在电力能源领域,VR 技术在高性能计算机和专有设备支…...
报错 -bash: wget: command not found
1、报错 -bash: wget: command not found 可以重装 wget 工具: 卸载 wget 工具 yum remove wget下载 wget 工具 yum -y install wget最后尝试 wget “url” 又OK了,一般是原来的wget初始化有文件损坏造成的。...
HashMap扩容和Redis中Dict 扩容
扩容时机: Hash Map:要在某个临界点进行扩容处理,该临界点就是HashMap中元素的数量在数值上等于threshold(table数组长度*加载因子) Dict: 当每次新增键值对的时 , 会检测 负载因子(LoadFactor) , 判断以…...
【Redis】内存数据库Redis进阶(Redis持久化)
目录 分布式缓存 Redis 四大问题Redis 持久化RDB (Redis DataBase)RDB执行时机RDB启动方式——save指令save指令相关配置save指令工作原理save配置自动执行 RDB启动方式——bgsave指令bgsave指令相关配置bgsave指令工作原理 RDB三种启动方式对比RDB特殊启动形式RDB优点与缺点 A…...
在PHP8中检测数据类型-PHP8知识详解
在PHP 8中,可以使用多种方法来检测数据类型。以下是常用的四种方法:使用 gettype() 函数、使用 is_* 系列函数、使用 get_debug_type() 函数、使用 get_class() 函数。 一、使用 gettype() 函数 gettype() 函数返回给定变量的数据类型。例如:…...
amoeba实现MySQL读写分离
amoeba实现MySQL读写分离 准备环境:主机A和主机B作主从配置,IP地址为192.168.131.129和192.168.131.130,主机C作为中间件,也就是作为代理服务器,IP地址为192.168.131.136。三台服务器操作系统为RHEL6.4 x86_64,为…...
angr学习-入门篇
前言: 资源链接:GitHub - jakespringer/angr_ctf(题库仓库,里面有个讲解angr的PPT,里面有官方的题解很详细) GitHub - Hustcw/Angr_Tutorial_For_CTF: angr tutorial for ctf 安装: 关于angr…...
基于java SpringBoot和HTML的博客系统
随着网络技术渗透到社会生活的各个方面,传统的交流方式也面临着变化。互联网是一个非常重要的方向。基于Web技术的网络考试系统可以在全球范围内使用互联网,可以在本地或异地进行通信,大大提高了通信和交换的灵活性。在当今高速发展的互联网时…...
动态sql以及常用的标签
什么是动态sql: 指根据不同的条件生成不同的sql 搭建环境: 建表: create table blog( id varchar(50) not null comment 博客id, title varchar(100) not null comment 博客标题, author varchar(30) not null comment 博客作者, create_ti…...
DID以及社交网络中的ZKP
1. 引言 本文关键术语为: Decentralized Identity (DID,去中心化身份) or self-sovereign identity (SSI,自治身份) :是一个基于开放标准的框架,使用自主、独立的标识符和可验证证书,实现可信的数据交换。…...
基于SWAT-MODFLOW地表水与地下水耦合
耦合模型被应用到很多科学和工程领域来改善模型的性能、效率和结果,SWAT作为一个地表水模型可以较好的模拟主要的水文过程,包括地表径流、降水、蒸发、风速、温度、渗流、侧向径流等,但是对于地下水部分的模拟相对粗糙,考虑到SWAT…...
2023拒绝内卷!两年转行网络安全真实看法!
我目前转行网络安全两年,没啥天分,全靠努力,基本能够得上中级的水平了。看到大家对转行网络安全挺感兴趣,也有挺多争议,想把我的建议和经验告诉大家。 有很多人觉得网络安全已经饱和了,现在选择这个工作&a…...
【SA8295P 源码分析】57 - libDSI_MAX96789_0.so驱动库 之 QDI_Panel_Init 显示屏初始化函数 代码分析
【SA8295P 源码分析】57 - libDSI_MAX96789_0.so驱动库 之 QDI_Panel_Init 显示屏初始化函数 代码分析 一、QDI_Panel_Init() 显示屏初始化函数:Panel_DSI_MAX96789_0_Init()二、QDI_Panel_SetPower() 显示屏初始化:Panel_DSI_MAX96789_0_PowerLCD()三、QDI_Panel_GetInfo() …...
IDEA偶尔编译的时候不识别lombok
偶尔IDEA启动项目的时候会识别不到lombok,识别不到get()跟set()方法 方案 在settings添加下面代码 -Djps.track.ap.dependenciesfalse...
rust学习-构建服务器
单线程server 服务器会依次处理每一个请求,在完成第一个连接的处理之前不会处理第二个连接 // cat main.rs use std::io::prelude::*; use std::net::TcpListener; use std::net::TcpStream;fn main() {let listener TcpListener::bind("127.0.0.1:7878&quo…...
Java并发----进程、线程、并行、并发
一、进程与线程 进程 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的 当一个程序被运行…...
【计算机网络】第 4 课 - 物理层
欢迎来到博主 Apeiron 的博客,祝您旅程愉快 ! 时止则止,时行则行。动静不失其时,其道光明。 目录 1、物理层的基本概念 2、物理层协议的主要任务 3、物理层任务 4、总结 1、物理层的基本概念 在计算机网络中,用来…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
深入理解JavaScript设计模式之单例模式
目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式(Singleton Pattern&#…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
