volatile 关键字的语义和用法
来源:互联网 发布:淘宝头条 编辑:程序博客网 时间:2024/06/10 02:26
首先我们来看volatile自身的语义:
1,读volatile变量总是可以读到任何线程最近一次对该变量的写入。这意味着java编译器不会优化volatile变量的读写,每次对volatile变量的写入都会写入主内存,每次读取volatile变量也都会从主内存中读取而不会把该变量暂存在工作内存(寄存器)中。这就可以保证如下代码可以工作:
private volatile boolean flag = false;//线程1一直执行如下循环等待flag变为true:while (flag == false) { doSomething();}//线程2在某个时刻执行执行:flag = true;
也就是说线程2在某时刻设置flag的值为true后,线程1可以立即感知到。如果flag变量没有volatile修饰,则线程1在线程2设置flag为true后不一定能够感知到,这样可能导致线程1永远跳不出那个循环。
2,对volatile变量的单个读或单个写操作都是原子操作。假如有如下代码在两个线程中同时执行:
private volatile long count;//线程1count = 0x1234567890abcdef;//线程2count = 0x1111111122222222;
由于这两个线程中执行的都是单个写操作,本条语义保证了其原子性,所以count最后的值只可能是0x1234567890abcdef或0x1111111122222222这两者之一,不可能出现诸如0x1234567822222222这样的非法值(如果count变量没有volatile修饰的话,则可能出现这种非法值)。同理,如果不保证读操作是原子性的,则读的时候可能读到非法值,即刚好读了4个字节,然后中间插入了对该变量的写入,然后再读剩下的4个字节。
重要:这条语义只是说明单个读单个写是原子的,并不保证又读又写这种复合操作是原子的。比如 它并不会保证couter++是原子的,因为counter++需要2次访问内存,即首先从内存中读取该值,然后加1,然后把结果写入内存。所以即使是存在volatile修饰的counter变量我们也不能在多个线程中没有同步手段的保护下并发执行counter++。
下面来看volatile变量对其它变量可见性的影响:
3,其它线程在观察到线程A对volatile变量v的修改之时(后),也一定能够观察到线程A对源代码中位于变量v之前的其它变量的修改。文字表达可能有点抽象,看下面的代码:
int a, b;volatile int c;//线程1执行:a = 1;b = 2;c = 3;//线程2执行if (c == 3) { //使用a和b}
这段代码可以保证线程2的if条件为真时,也就是当c等于3时,a一定等于1, b一定等2。如果c不是volatile变量,则上述结论是不一定成立的。
从编程的角度来说,理解上面这几条语义之后就可以写出正确使用volatile变量的代码了。
我认为这是区分C程序员和嵌入式系统程序员第一个问题。
搞嵌入式的小伙伴们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
一.volatile的两个用途:
1.告诉compiler不能做任何优化
比如要往某一地址送两指令:
int *ip =...; //设备地址
*ip = 1; //第一个指令
*ip = 2; //第二个指令
以上程序compiler可能做优化而成:
int *ip = ...;
*ip = 2;
结果第一个指令丢失。如果用volatile, compiler就不允许做任何的优化,从而保证程序的原意:
volatile int *ip = ...;
*ip = 1;
*ip = 2;
即使你要compiler做优化,它也不会把两次付值语句间化为一。它只能做其它的优化。这对device driver程序员很有用。
2.表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用。
如 volatile char a;
a=0;
while(!a){
//do some things;
}
doother();
如果没有 volatile doother()不会被执行
二.面试时被问道的几个问题:
1)一个参数既可以是const还可以是volatile吗?解释为什么。
2); 一个指针可以是volatile 吗?解释为什么。
3); 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2); 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
我希望本文可以帮助你提升技术水平。那些,感觉学的好难,甚至会令你沮丧的人,别担心,我认为,如果你愿意试一试本文介绍的几点,会向前迈进,克服这种感觉。这些要点也许对你不适用,但你会明确一个重要的道理:接受自己觉得受困这个事实是摆脱这个困境的第一步。
注:加群要求 学习交流群:450936584
1、想学习JAVA这一门技术, 对JAVA感兴趣,想从事JAVA工作的。
2、工作0-5年,感觉自己技术不行,想提升的
3、如果没有工作经验,但基础非常扎实,想提升自己技术的。
4、还有就是想一起交流学习的。
5、小号加群一律不给过,谢谢。
群内每天会分享最新的视频和资料,可以免费领取学习视频和资料
转发此文章请带上原文链接,否则将追究法律责任!