volatile的介绍

volatile的本意是“易变的”

由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:

static int i=0;

int main(void)
{
...
while (1)
{
if (i) dosomething();
}
}

/* Interrupt service routine. */
void ISR_2(void)
{
i=1;
}

程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此
可能只执行一次对从i到某寄存器的读***作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被
调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写***作都不会被优化(肯定执行)。此例中i也应该如此说明。

一般说来,volatile用在如下的几个地方:

1、中断服务程序中修改的供其它程序检测的变量需要加volatile;

2、多任务环境下各任务间共享的标志应该加volatile;

3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实
现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。



volatile 的含义
volatile总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化,volatile的字面含义是易变的,它有下面的作用:

1 不会在两个***作之间把volatile变量缓存在寄存器中。在多任务、中断、甚至setjmp环境下,变量可能被其他的程序改变,编译器 自己无法知道,volatile就是告诉编译器这种情况。

2 不做常量合并、常量传播等优化,所以像下面的代码:
volatile int i = 1;
if (i > 0) ...

if的条件不会当作无条件真。

3 对volatile变量的读写不会被优化掉。如果你对一个变量赋值但后面没用到,编译器常常可以省略那个赋值***作,然而对Memory Mapped IO的处理是不能这样优化的。

前面有人说volatile可以保证对内存***作的原子性,这种说法不大准确,其一,x86需要LOCK前缀才能在SMP下保证原子性,其二,RISC根本不能对内存直接运算,要保证原子性得用别的方法,如atomic_inc。

对于jiffies,它已经声明为volatile变量,我认为直接用jiffies++就可以了,没必要用那种复杂的形式,因为那样也不能保证原子性。

你可能不知道在Pentium及后续CPU中,下面两组指令

inc jiffies
;;
mov jiffies, %eax
inc %eax
mov %eax, jiffies

作用相同,但一条指令反而不如三条指令快。

 


用volatile关键字定义变量,相当于告诉编译器,这个变量的值会随时发生变化,每次使用时都需要去内存里重新读取它的值,并不要随意针对它作优化。

不使用volatile定义的变量,可能因为编译器优化而出现一些问题,具体可以看给出的参考资料,里面有一个例子。

建议使用volatile变量的场所:
(1) 并行设备的硬件寄存器
(2) 一个中断服务子程序中会访问到的非自动变量(全局变量)
(3) 多线程应用中被几个任务共享的变量  

<think>嗯,用户让我介绍一下volatile关键字。好的,首先我要确定用户的基础,可能他正在学习Java并发或者遇到了多线程的问题。volatile在Java中确实是一个比较重要的概念,但有时候容易让人混淆。 首先,我需要从基础说起,解释volatile的作用。用户可能知道变量在多线程中的可见性问题,比如一个线程修改了变量,另一个线程可能看不到变化。volatile能保证可见性,这点要强调清楚。 然后,要提到volatile的另一个特性,禁止指令重排序。这可能涉及到JVM的内存模型,比如happens-before原则。需要举例说明,比如单例模式中的双重检查锁定,这时候volatile的作用就是防止对象初始化时的重排序问题。 接下来,用户可能想知道volatile和synchronized的区别。这时候需要比较两者的不同,比如synchronized保证原子性和可见性,而volatile只保证可见性和顺序性。但要注意,volatile不保证原子性,比如i++这样的操作,这时候需要用synchronized或者原子类。 还要考虑用户的实际应用场景,比如状态标志位。这种情况下使用volatile是合适的,因为只是简单的赋值操作,不涉及复合操作。但如果是需要原子性,比如计数器,volatile就不够了,这时候需要AtomicInteger。 另外,用户可能会误解volatile能解决所有并发问题,需要指出它的局限性。例如,虽然它能保证单次读写的原子性,但像自增这种复合操作还是需要同步机制。 可能用户遇到过缓存一致性问题,或者线程间通信的问题。这时候volatile的正确使用就显得重要了。需要给出代码示例,比如状态标志位的例子,说明如何正确使用volatile。 还要提到内存屏障的概念,解释volatile如何通过内存屏障来实现可见性和禁止重排序。但不需要深入底层细节,保持解释在应用层面即可。 最后,总结volatile的适用场景和注意事项,帮助用户做出正确的选择。比如在性能敏感的场景下,如果只需要可见性,volatile比synchronized更轻量,但如果有复合操作,就需要其他方式。 需要确保解释清晰,避免技术术语过多,但又要准确。可能用户需要的是快速理解和使用,所以重点放在应用场景和常见误区上。</think>以下是对`volatile`关键字的详细解析,采用分层结构说明其工作原理和适用场景: $$ \begin{array}{|c|c|} \hline \text{特性维度} & \text{技术描述} \\ \hline \end{array} $$ 一、底层原理 ```mermaid graph LR A[volatile变量] --> B[强制所有读写操作直接访问主内存] B --> C[禁止CPU缓存优化] C --> D[插入内存屏障] D --> E[保证可见性+禁止指令重排序] ``` 二、核心特性 1. **可见性保证** - 任何线程修改`volatile`变量后,其他线程**立即**可见新值 - 对比普通变量:修改后可能暂存于CPU缓存/寄存器,其他线程无法感知 2. **有序性约束** - 通过内存屏障(Memory Barrier)实现: ```java StoreStore屏障 | volatile写 | StoreLoad屏障 LoadLoad屏障 | volatile读 | LoadStore屏障 ``` - 禁止JVM和CPU进行指令重排序优化 三、典型应用场景 1. **状态标志位** ```java volatile boolean shutdownRequested; public void shutdown() { shutdownRequested = true; } public void doWork() { while(!shutdownRequested) { // 执行任务 } } ``` 2. **双重检查锁定单例模式** ```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防止指令重排序 } } } return instance; } } ``` 四、与synchronized对比 | 特性 | volatile | synchronized | |---------------------|-------------------------------|-----------------------| | 原子性 | 仅保证单次读/写原子性 | 保证代码块原子性 | | 可见性 | 立即可见 | 释放锁时同步主内存 | | 阻塞特性 | 非阻塞 | 线程阻塞 | | 适用场景 | 单一状态变更 | 复合操作/临界区 | 五、使用注意事项 1. **不适用场景** - 自增操作(i++)等非原子复合操作 - 多变量关联的状态变更 2. **性能影响** - 每次访问直接读写主内存,比普通变量慢约20% - 比`synchronized`轻量,但过度使用仍会影响性能 六、内存语义(JMM层面) 1. **写操作语义** - 刷新该变量到主内存 - 使其他线程缓存失效 2. **读操作语义** - 从主内存重新加载最新值 - 后续操作可见该变量的最新状态 七、现代硬件支持 1. **MESI协议增强**:CPU通过缓存一致性协议自动维护volatile可见性 2. **内存模式优化**:x86架构下StoreLoad屏障开销较大(使用`lock`指令实现) 根据Java规范,volatile的正确使用需要满足以下条件: $$ \text{适用条件} = \begin{cases} \text{变量不参与不变式约束} \\ \text{变量状态独立于其他变量} \\ \text{变量访问不需要加锁} \end{cases} $$ 建议结合`java.util.concurrent.atomic`包中的原子类(如`AtomicInteger`)使用,在保证线程安全的同时获得更好的性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值