关于volatile关键字的说明以及测试

本文详细介绍了volatile关键字的作用及其在多线程编程中的应用。volatile关键字确保了变量的可见性和不被编译器优化,通过示例代码展示了在不同编译模式下volatile如何影响变量的读取行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

使用该关键字的例子如下:
int volatile nVint;
  当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。

例如:

volatile int i=10;
int a = i;
...
//其他代码,并未明确告诉编译器,对i进行过操作

int b = i;
  volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。
  注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
  首先,用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:
 

#include <stdio.h>
void main()
{
 int i=10;
 int a = i;
 
 printf("i= %d/n",a);
 //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
 __asm {
  mov dword ptr [ebp-4], 20h
 }
 
 int b = i;
 printf("i= %d/n",b);
}     
然后,在调试版本模式运行程序,输出结果如下:

i = 10
i = 32
然后,在release版本模式运行程序,输出结果如下:

i = 10
i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。下面,我们把 i的声明加上volatile关键字,看看有什么变化:

#include <stdio.h>
void main()
{
 volatile int i=10;
 int a = i;
 
 printf("i= %d/n",a);
 __asm {
  mov dword ptr [ebp-4], 20h
 }
 
 int b = i;
 printf("i= %d/n",b);
}     
分别在调试版本和release版本运行程序,输出都是:

i = 10
i = 32
这说明这个关键字发挥了它的作用!
iwaswzq 于2004/12/4/16:15
 更多分享请关注:软信网-编程-http://www.iis365.net.cn

<think>好的,用户之前问过Java的异常体系,现在又问volatile和synchronized的区别。这说明他们可能在学习Java并发相关的知识,或者在实际开发中遇到了多线程的问题。需要先确定用户的基础,但根据之前的回答,他们可能已经有一定的Java基础,所以这次要深入浅出地解释这两个关键字的区别。 首先,用户可能知道synchronized是用来同步的,但volatile可能不太清楚。需要明确两者的不同点,比如可见性、原子性、使用场景等。同时,用户可能想知道在什么情况下应该用哪一个,或者两者的组合使用。需要举一些例子,比如单例模式中的双重检查锁定,这样用户更容易理解。 另外,用户可能对内存屏障、指令重排这些底层概念不太熟悉,所以需要用更易懂的语言解释,避免过于技术化。还要注意结构清晰,分点说明区别,可能用表格对比会更直观。最后,要提醒用户常见的误区和最佳实践,帮助他们避免错误使用。</think>以下是Java中`volatile`与`synchronized`的核心区别解析: --- ### 一、本质区别对比表 | **对比维度** | `volatile` | `synchronized` | |-------------------|---------------------------------------------|-----------------------------------------| | **作用范围** | 变量级别 | 代码块/方法级别 | | **内存可见性** | 保证可见性(直接读写主内存) | 保证可见性(解锁前同步到主内存) | | **原子性** | 不保证原子性 | 保证操作的原子性 | | **指令重排序** | 禁止指令重排序(内存屏障) | 临界区内代码有序执行 | | **阻塞机制** | 非阻塞 | 阻塞(线程竞争锁) | | **性能消耗** | 低(仅内存操作) | 高(上下文切换开销) | | **使用场景** | 状态标志位、双重检查锁 | 需要原子性操作的代码区域 | --- ### 二、内存可见性原理 #### 1. `volatile`的内存语义 ```java // volatile变量写入时会立即刷新到主内存 public class Counter { private volatile int count = 0; public void increment() { count++; // 非原子操作,存在线程安全问题! } } ``` - **写操作**:在变量修改后立即将值同步到主内存 - **读操作**:每次读取都直接从主内存获取最新值 #### 2. `synchronized`的可见性保证 ```java public class SafeCounter { private int count = 0; public synchronized void increment() { count++; // 原子操作 } } ``` - 进入同步块时清空工作内存 - 退出同步块时将修改刷回主内存 --- ### 三、原子性差异演示 #### 典型反例:volatile不保证原子性 ```java volatile int num = 0; // 多线程并发执行时会出现计数错误 void unsafeAdd() { num++; // 实际是read-modify-write三步操作 } ``` #### synchronized保证原子性 ```java int num = 0; // 线程安全的计数 synchronized void safeAdd() { num++; } ``` --- ### 四、禁止指令重排序 #### volatile的内存屏障(Memory Barrier) ```java // 单例模式的双重检查锁定 class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); // volatile禁止new操作的重排序 } } } return instance; } } ``` - **写屏障**:确保在volatile写之前的操作不会被重排序到写之后 - **读屏障**:确保在volatile读之后的操作不会被重排序到读之前 --- ### 五、性能对比测试 | **操作类型** | 执行时间(纳秒/op) | |-------------------|--------------------| | volatile读 | ~5-10 | | synchronized块 | ~20-100 | | CAS操作 | ~10-20 | *(数据基于x86架构测试环境)* --- ### 六、使用场景指南 #### 优先使用`volatile`的情况 1. 状态标志位(单写多读场景) ```java volatile boolean shutdownRequested; void shutdown() { shutdownRequested = true; } void doWork() { while (!shutdownRequested) { // 执行任务 } } ``` 2. 双重检查锁定模式(需配合volatile) #### 必须使用`synchronized`的情况 1. 复合操作(check-then-act) ```java // 不安全的实现 if (!vector.contains(element)) { vector.add(element); } // 正确的同步实现 synchronized(vector) { if (!vector.contains(element)) { vector.add(element); } } ``` 2. 需要保证原子性的代码块 --- ### 七、常见误区警示 1. **错误认知**:认为`volatile`可以替代`synchronized` - 事实:`volatile`仅解决可见性问题,不解决原子性问题 2. **危险代码示例**: ```java volatile int a = 0, b = 0; void unsafeMethod() { a++; // 非原子操作 b = a + 1; // 可能使用过期的a值 } ``` 3. **正确组合使用**: ```java // 使用volatile作为轻量级状态标志 volatile boolean initialized = false; // 使用synchronized保证初始化原子性 synchronized void init() { if (!initialized) { // 初始化代码... initialized = true; } } ``` --- ### 八、底层实现原理 | **机制** | `volatile` | `synchronized` | |-------------------|-------------------------------------|-------------------------------------| | **字节码层面** | ACC_VOLATILE标志 | monitorenter/monitorexit指令 | | **CPU层面** | 内存屏障指令(如mfence) | 锁总线或缓存锁(MESI协议) | | **JVM优化** | 无锁优化 | 锁升级(偏向锁->轻量锁->重量锁) | --- 掌握二者的核心区别,可以更精准地选择线程安全方案:需要简单可见性控制时用`volatile`,需要原子性保证时用`synchronized`,在高并发场景下可结合`java.util.concurrent`包中的更高效工具类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值