volatile变量类型的详细说明

本文详细探讨了Java中的volatile关键字,它提供了轻量级的同步机制。文章指出,volatile确保了变量的可见性,但并不保证原子性,因此在并发情况下仍可能存在安全性问题。通过volatile、synchronized和final三种方式可以实现可见性。示例代码展示了volatile在并发计数场景下的不安全性。

关键字volatile是java虚拟机提供的最轻量级的同步机制。但是许多人对它的理解还是不正确,不完整。

下面详细讲解一下:

由于关键字volatile关键没有被更好的理解,因此许多程序员遇到处理多线程数据竞争问题的时候都是有synchronzied来同步,仅为读写一两个实例域就使用同步,显得开销有些大了。因此需要深入理解此关键字。

Java内存模型对volatile专门定义了一些特殊的访问规则

当一个变量被定义为volatile时,此变量具备两种特性:

1.可见性:可见性在java内存模型中有定义,可以参看。

普通变量则没有,他们在线程之间的交互是通过主内存来完成,volatile变量则是通过主内存完成交换,但是两者区别在于volatile变量能立即同步到主内存中,当一个线程修改变量的变量的时候,立刻会被其他线程感知到。

特别注意一点:volatile变量的可见性经常性被误解,认为,valotile变量在各个线程中是一致的。所以基于volatile变量是安全的。这种认为是错误的。论据是正确的,但是得出的是安全的就不正确了。不会存在不一致性问题(在各个的工作内存中可以存在不一致的情况,但是由于每次使用之前都要刷新,执行引擎看不到不一致的问题,因此认为不存在不一致的问题)但是java里面的运算中并非原子操作,导致volatile变量的运算在并发下一样不安全。


实现可见性方式: 1.volatile   2.synchronized    3.final


下面代码演示不安全的情况:

public class VolatileTest {

private static final int THREAD_NUM =20;

public static volatile int num= 0;

public static void increase(){

num++ ;

}

public static void main(String[] args) {

Thread[] threads = new Thread[THREAD_NUM];

for (int i = 0; i < THREAD_NUM; i++) {

threads[i] = new Thread(new Runnable() {

@Override

public void run() {

for(int i=0;i<1000;i++){

increase();

}

}

});

threads[i].start();

}

while(Thread.activeCount()>1){

Thread.yield();

}

System.out.println(num);

}

}

输出的正确答案应该是200000,但是每次输出都小于200000,问题在于increase()方法。我们用javap发编译看一下发现就increase()方法在Class中文件有四条字节码组成。但是这种字节码测试并不能说明这条指令是原子操作,因为一条字节码指令解释执行时,解释器将要运行许多行代码才能实现它的语义,当编译执行时,一条也可能被住转化为若干本地机器指令。

当不符合一下规则是还是使用synchronized或java.util.concurrent中的原子类。
1.运算结果并不依赖变量的当前值,或者能够确保单一的线程修改变量的

2.变量不需要与其他的状态变量共同参与不变约束。

2.禁止指令重排序优化

普通变量仅仅会保证在该方法的执行过程中所依赖复制结果的地方都能获取到正确的结果,而不能保证变量复制操作的顺序与程序代码中的执行顺序一致。

<think>我们正在讨论volatile变量的使用举例。 volatile关键字用于告诉编译器,该变量的值可能会在程序的控制之外被改变(例如,由硬件、中断服务程序、多线程环境中的其他线程等)。因此,编译器不能对该变量的访问进行优化(如缓存到寄存器,或者重排读写顺序)。 举例场景: 1. 硬件寄存器映射:在嵌入式系统中,硬件寄存器的值可能随时改变,使用volatile可以确保每次访问都从内存中读取。 2. 中断服务程序修改的变量:主程序中的变量可能被中断服务程序修改,使用volatile防止编译器优化掉对变量的访问。 3. 多线程共享变量:在没有使用锁的情况下,多个线程可能访问同一个变量,使用volatile可以确保每次从内存中读取(但注意,volatile不能替代同步原语,因为它不保证操作的原子性)。 下面给出两个代码示例: 示例1:嵌入式系统中的硬件寄存器访问 ```c // 假设0x40000000是一个硬件寄存器的地址 #define HW_REGISTER ((volatile unsigned int *)0x40000000) int main() { // 不断读取该寄存器的值,直到其变为非零 while (*HW_REGISTER == 0) { // 空循环,等待硬件信号 } // 当寄存器值非零时,执行后续操作 return 0; } ``` 如果不使用volatile,编译器可能会将读取操作优化为只进行一次,然后使用缓存的值,导致无限循环。 示例2:多线程中标志位的使用(注意:仅用volatile不足以保证线程安全,这里仅用于说明volatile的可见性) ```c volatile bool flag = false; // 线程1 void thread1() { // 执行一些操作 flag = true; // 设置标志 } // 线程2 void thread2() { while (!flag) { // 等待flag变为true } // 继续执行 } ``` 这里使用volatile确保线程2每次都会从内存中读取flag的值,而不是使用缓存。但是,如果两个线程同时运行在多个处理器上,现代处理器可能会重排内存访问顺序,因此还需要内存屏障(memory barrier)来保证顺序。所以,在真正的多线程编程中,通常使用互斥锁或原子操作。 注意事项: - volatile不能保证操作的原子性(如自增操作)。 - volatile不会阻止编译器对非volatile变量的优化,因此只对标记为volatile变量有效。 §§相关问题§§ 1. 在C语言中,volatileatomic类型在并发编程中的主要区别是什么? 2. 如何利用内存屏障配合volatile变量实现多线程环境下的正确同步? 3. 在嵌入式系统中,除了volatile,还有哪些方法可以安全访问硬件寄存器? 4. volatile关键字在JavaC/C++中的语义有何不同? 5. 为什么编译器优化会导致对硬件寄存器的多次读取被优化为单次读取?</think>### `volatile`型变量的使用举例 `volatile`是编程中的关键字,用于告知编译器该变量可能被外部因素(如硬件、中断、多线程)意外修改,**禁止编译器优化对其的读写操作**(如缓存到寄存器或重排指令)。以下是典型应用场景及代码示例: --- #### **1. 硬件寄存器访问(嵌入式系统)** **场景**:操作映射到内存地址的硬件寄存器(如传感器状态寄存器)。 **示例**: ```c // 定义硬件寄存器地址(volatile防止编译器优化读取) volatile uint32_t* sensor_reg = (volatile uint32_t*)0x4000F000; void read_sensor() { while ((*sensor_reg & 0x01) == 0) { // 必须每次从内存读取实时值 // 等待传感器就绪位(硬件可能随时修改该位) } uint32_t data = *sensor_reg; // 读取传感器数据 } ``` > **关键点**:若未用`volatile`,编译器可能将`*sensor_reg`缓存到寄存器,导致死循环。 --- #### **2. 中断服务程序(ISR)共享变量** **场景**:主循环与中断服务程序通过全局变量通信。 **示例**: ```c volatile bool data_ready = false; // 标记数据是否就绪 // 中断服务程序(硬件触发) void __isr() { data_ready = true; // 外部修改 } int main() { while (!data_ready) { // 必须每次检查内存中的实际值 // 等待中断触发 } process_data(); // 处理数据 } ``` > **关键点**:编译器可能优化`while (!data_ready)`为`if (!data_ready) while(1);`,导致无法响应中断。 --- #### **3. 多线程共享标志位** **场景**:线程A修改标志,线程B轮询该标志(无锁同步)。 **示例**: ```c volatile bool stop_requested = false; // 停止请求标志 // 工作线程 void* worker_thread(void* arg) { while (!stop_requested) { // 每次检查内存最新值 perform_task(); } return NULL; } // 控制线程 void request_stop() { stop_requested = true; // 请求停止工作线程 } ``` > **注意**:`volatile`仅保证可见性,不保证原子性(如自增操作仍需原子变量或锁)。 --- #### **4. 内存映射I/O操作** **场景**:向设备控制寄存器写入命令序列。 **示例**: ```c volatile uint8_t* ctrl_reg = (volatile uint8_t*)0x8000; void send_command(uint8_t cmd) { *ctrl_reg = cmd; // 写入命令 *ctrl_reg = cmd | 0x80; // 必须按顺序执行写入(禁止编译器重排) } ``` > **关键点**:`volatile`强制按代码顺序执行读写,防止编译器重排指令。 --- ### **注意事项** - **非原子性**:`volatile`不保证操作的原子性(如`i++`需用原子类型)。 - **替代方案**: - C++11+:优先使用`std::atomic`(提供原子性与内存顺序控制)。 - 嵌入式:结合内存屏障(`__asm__ volatile("" ::: "memory")`)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值