关于volatile关键字的一些个人理解

本文深入探讨Java并发编程中的Volatile关键字,解析其在内存模型中的作用,包括可见性和有序性保证,以及与Synchronized的区别。同时,介绍了Volatile在实际场景中的应用,如volatile bean模式和开销较低的读-写锁策略。

前言

众做周知,JAVA语言做并发编程最适合不过了,而且提供了众多的关键字来保证在高并发下程序性的稳定性,syschronized和volatile都是一种锁的机制,但是有syschronized为什么还要有volatile呢??其实,关键点就在于,性能问题

谈这个关键字,主要还是要从下面几点讲起

 一、内存模型的相关概念      

二、并发编程中的三个概念  

  三、深入剖析volatile关键字

 四、使用volatile关键字的场景  

  五、synchronize与volatile的区别

一、内存模型的相关概念

(1)不同的平台,内存模型是不一样的,但是jvm的内存模型规范是统一的       

(2)线程安全无非是要控制多个线程对某个资源的有序访问或修改。总结java的内存模型多线程下,要解决两个主要的问题:可见性和有序性。          

(3)我们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内存的。JVM定义了自己的内存模型,屏蔽了底层平台内存管理细节

内存模型的工作原理

线程执行的时候,将首先从主内存读值,再load到工作内存中的副本中,

然后传给处理器执行,执行完毕后再给工作内存中的副本赋值,

随后工作内存再把值传回给主存,主存中的值才更新

 二、并发编程中的三个概念

1、原子性

只有简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作

当然,以下这些都是原子性操作,有兴趣的可以自行了解

LOCK(锁定),UNLOCK(解锁),read(读),load(载入),use(使用), assign(赋值),store(存储),write(写入)

2、可见性

多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值

3、有序性

在Java内存模型中,允许编译器和处理器对指令进行重排序,(JIT认为可以优化下就给你改变了执行顺序)

但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性

三、volatile关键字

Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。

当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,

因此不会将该变量上的操作与其他内存操作一起重排序

volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1、 保证多线程下的可见性 2、 禁止进行指令重排序(即保证有序性)。

四、volatile使用前提

1、对变量的写操作不依赖于当前值

2、该变量没有包含在具有其他变量的不变式中 简单来说,就是保证该操作是原子性操作

五、volatile的使用场景

1、volatile bean 模式 

volatile bean 模式适用于将 JavaBeans 作为“荣誉结构”使用的框架。在 volatile bean 模式中,JavaBean 被用作一组具有 getter 和/或 setter 方法 的独立属性的容器。volatile bean 模式的基本原理是:很多框架为易变数据的持有者(例如 HttpSession)提供了容器,但是放入这些容器中的对象必须是线程安全的。

在volatile bean 模式中,JavaBean 的所有数据成员都是 volatile 类型的,并且 getter 和 setter 方法必须非常普通 —— 除了获取或设置相应的属性外,不能包含任何逻辑。此外,对于对象引用的数据成员,引用的对象必须是有效不可变的。(这将禁止具有数组值的属性,因为当数组引用被声明为 volatile 时,只有引用而不是数组本身具有 volatile 语义)。对于任何 volatile 变量,不变式或约束都不能包含 JavaBean 属性

2、开销较低的读-写锁策略

之所以将这种技术称之为 “开销较低的读-写锁” 是因为您使用了不同的同步机制进行读写操作。因为本例中的写操作违反了使用 volatile 的第一个条件,因此不能使用 volatile 安全地实现计数器 —— 您必须使用锁。然而,您可以在读操作中使用 volatile 确保当前值的可见性,因此可以使用锁进行所有变化的操作,使用 volatile 进行只读操作。其中,锁一次只允许一个线程访问值,volatile 允许多个线程执行读操作,因此当使用 volatile 保证读代码路径时,要比使用锁执行全部代码路径获得更高的共享度 —— 就像读-写操作一样。然而,要随时牢记这种模式的弱点:如果超越了该模式的最基本应用,结合这两个竞争的同步机制将变得非常困难。

synchronized和volatile的异同

java中能创建volatile数组吗?

如果将volatile修饰在数组上,它只对引用这个数组的属性本身有效,而对这个数组里面引用的对象,是没有效果的

转载请注明出处,掌声送给社会人

<think>嗯,用户想要了解Java中volatile关键字的详细解释。从引用内容来看,用户提供了五个相关引用,这些引用涵盖了volatile的作用、原理、使用场景和注意事项。 用户可能是个Java开发者,正在学习多线程编程,或者在工作中遇到了线程可见性问题。ta特意要求"详细解释",说明需要深入理解而不仅仅是表面定义。从引用[3]提到的"不太推荐生产环境使用"来看,用户可能还关心实际应用中的取舍。 这些引用内容质量不错:[1][2]介绍了基本概念,[4][5]解释了内存模型底层机制,[3]则提供了宝贵的实践建议。特别是引用[4]提到的八大原子操作和引用[5]描述的Java内存模型,这些是理解volatile的底层基础。 需要重点覆盖几个方面:首先是可见性和禁止重排序这两个核心特性,要用内存模型原理解释清楚;其次要强调原子性缺失这个关键限制;最后要给出具体的使用场景和替代方案。用户引用[3]特别提醒了volatile的陷阱,这点一定要强调。 关于内存模型的解释,可以结合引用[4][5]的术语,但要用更易懂的方式表述。比如把"主内存-工作内存"类比为"中央仓库-个人工作台",把内存屏障比喻为"围栏"。用户提供的双重检查锁定示例很经典,但需要补充说明为什么需要volatile。 考虑到用户可能实际编码,应该给出代码示例。但要注意避免引用[3]警告的陷阱,比如用原子类替代的场景。安全提示也很重要,要像引用[3]那样明确建议优先使用锁机制。 </think>以下是Java中`volatile`关键字的详细解析: --- ### **一、核心作用** 1. **可见性保证** 当线程修改`volatile`变量时,新值**立即写入主内存**;其他线程读取该变量时,**强制从主内存重新加载最新值**。解决多线程下共享变量可见性问题[^5]。 *示例:* ```java volatile boolean flag = false; // 线程A修改 flag = true; // 写入主内存 // 线程B读取 if (flag) { // 从主内存加载最新值 // 执行操作 } ``` 2. **禁止指令重排序** 通过插入**内存屏障(Memory Barrier)**,禁止编译器和CPU对`volatile`变量相关指令的重排序优化,确保操作顺序符合预期[^2]。 --- ### **二、底层原理(Java内存模型-JMM)** 根据JMM规范[^4][^5]: 1. 线程操作变量需经过 **8种原子操作**: `lock` → `read` → `load` → `use` → `assign` → `store` → `write` → `unlock` 2. `volatile`变量操作的特殊约束: - **写操作**:`assign`后必须立即执行`store`+`write`(同步到主内存) - **读操作**:`use`前必须执行`load`+`read`(从主内存加载) 3. 内存屏障规则: - 写操作后插入`StoreLoad`屏障,确保写入对其他线程可见 - 读操作前插入`LoadLoad`/`LoadStore`屏障,防止重排序 --- ### **三、典型应用场景** 1. **状态标志位** ```java volatile boolean shutdownRequested; public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { // 及时感知状态变化 // 业务逻辑 } } ``` 2. **双重检查锁定(DCL单例)** ```java class Singleton { private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); // volatile防止初始化重排序 } } } return instance; } } ``` --- ### **四、局限性及替代方案** 1. **不保证原子性** 复合操作(如`i++`)仍需同步: ```java volatile int count = 0; count++; // 非原子操作(实际包含读-改-写三步) ``` - **替代方案**:使用`java.util.concurrent.atomic`包中的原子类(如`AtomicInteger`)[^3] 2. **适用场景有限** - 适合一写多读场景,多线程写竞争时需加锁(`synchronized`或`Lock`)[^3] - 复杂同步逻辑优先使用`java.util.concurrent`工具类 --- ### **五、与`synchronized`对比** | **特性** | `volatile` | `synchronized` | |------------------|--------------------------------|---------------------------------| | 可见性 | 直接保证 | 通过锁释放前写回主内存保证 | | 原子性 | 不保证 | 保证代码块原子性 | | 指令重排序 | 禁止相关重排序 | 临界区内禁止重排序 | | 阻塞行为 | 非阻塞 | 线程阻塞 | | 适用场景 | 简单状态标记、DCL单例 | 复杂同步逻辑 | --- ### **注意事项** - **谨慎使用**:仅当明确需要轻量级可见性/有序性时使用,避免牺牲正确性[^3] - **替代方案优先**:高并发场景推荐使用`ConcurrentHashMap`、`AtomicLong`等线程安全容器 - **性能考量**:`volatile`读操作接近普通变量,写操作稍慢(因内存屏障) > **关键总结**:`volatile`是**轻量级的线程通信机制**,通过强制主内存读写和禁止重排序提供可见性与有序性,但无法替代锁解决原子性问题[^2][^5]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值