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、小号加群一律不给过,谢谢。

群内每天会分享最新的视频和资料,可以免费领取学习视频和资料

转发此文章请带上原文链接,否则将追究法律责任!

阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 皇子被当尿壶 丈夫拿我当尿壶 做尿壶尿液灌满肚子h 女士尿壶 肉尿壶 女式尿壶 太子被当尿壶 女式尿壶接尿方法步骤 尿壶哪里买 男士尿壶 夜壶图片 做我的尿壶 尿壶图片 女性尿壶 儿童尿壶 女用夜壶 男人尿壶 车载尿壶 男性尿壶 夜壶是什么 不锈钢尿壶 成人尿壶 老人用的尿壶 夜壶理论 夜壶论 给霸道少爷当尿壶 拿少爷的嘴当尿壶 贱人爬过来当尿壶 主人惩罚奴儿当女体尿壶 尿崩 尿崩能自己恢复吗 尿布皮炎 尿布疹 尿布皮疹 尿布疹图片 尿布叠法 尿布怎么叠 尿不湿尿布 尿布的叠法 尿布式罚羞 纸尿布