Volatile个人理解
Posted Ioading
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Volatile个人理解相关的知识,希望对你有一定的参考价值。
http://www.cnblogs.com/dolphin0520/p/3920373.html
通过上面的阅读,记录以下个人理解,仅用以后自己复习。
volatile
用途:java多线程
修饰变量 ->
原子性 (与参考博客的理解不同,为个人理解,正确性待考究)
第一次阅读参考的博客的时候,没有发现疑问,这次重新看了一下,对参考博客上面 volatile保证原子性 这一段的分析产生不理解的地方,
参考的博客认为不能保证原子性是由于不同线程在改变volatile 变量前,已经读取了目标变量的值到工作内存中(高速缓存),所以会发生覆盖,
不理解的地方是既然修改了 volatile 变量,那么其它线程中的目标缓存行应该会无效,不应该会发生直接覆盖的情况,想了一下,可能是在A线
程改变volatile 变量前,B线程读取了volatile 变量的值,并完成了改值,但是并没有刷新到主存中,仍在高速缓存中,然后A线程完成对volatile
变量的改值并刷新到主存中,此时,由于volatile的原因,B线程在接着执行下一步操作时(改值之后从高速缓存刷新到主存中),但是由于目标
缓存行无效,所以刷新主存中的volatile 变量值到B线程的高速缓存中,再执行赋值到主存的操作,这样就等于把B线程中的改值操作覆盖掉了。
可见性
1.(变更后)即时更新
2.需要时重新读取
(需要注意:不读不写不违反volatile, 例如 线程A读取完成后,等待但不更新/ 同时,线程B读取并更新, 这时线程A对本身进行运算,仍会正常更新, 违反原子性)
有序性
3.限制编译器和处理器对指令进行重排序
(以volatile 修饰的地方为分隔线, 在某种程度上分为三部分, 并保证每部分的执行顺序不被改变)
int test = 10;
test++; test -> 11
volatile result = test--; result -> 10
test++; test -> 11
test--; test -> 10
如果在第三行用volatile 修饰,可以看作把代码分为三部分A(1,2行), B(3行), C(4,5行), 每部分的执行顺序不可以改变。每部分当中仍然遵循这个原则。
下面是转载的一些例子
当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。举个简单的例子,比如下面的这段代码:
1
|
i = i + 1 ; |
当线程执行这个语句时,会先从主存当中读取i的值,然后复制一份到高速缓存当中,然后CPU执行指令对i进行加1操作,然后将数据写入高速缓存,最后将高速缓存中i最新的值刷新到主存当中。
缓存一致性协议。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
volatile能保证对变量的操作是原子性吗?
下面看一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class Test { public volatile int inc = 0 ; public void increase() { inc++; } public static void main(String[] args) { final Test test = new Test(); for ( int i= 0 ;i< 10 ;i++){ new Thread(){ public void run() { for ( int j= 0 ;j< 1000 ;j++) test.increase(); }; }.start(); } while (Thread.activeCount()> 1 ) //保证前面的线程都执行完 Thread.yield(); System.out.println(test.inc); } } |
大家想一下这段程序的输出结果是多少?也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字。
可能有的朋友就会有疑问,不对啊,上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000。
这里面就有一个误区了,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。
在前面已经提到过,自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:
假如某个时刻变量inc的值为10,
线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;
然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。
然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。
那么两个线程分别进行了一次自增操作后,inc只增加了1。
解释到这里,可能有朋友会有疑问,不对啊,前面不是保证一个变量在修改volatile变量时,会让缓存行无效吗?然后其他线程去读就会读到新的值,对,这个没错。这个就是上面的happens-before规则中的volatile变量规则,但是要注意,线程1对变量进行读取操作之后,被阻塞了的话,并没有对inc值进行修改。然后虽然volatile能保证线程2对变量inc的值读取是从内存中读取的,但是线程1没有进行修改,所以线程2根本就不会看到修改的值。
根源就在这里,自增操作不是原子性操作,而且volatile也无法保证对变量的任何操作都是原子性的。
以上是关于Volatile个人理解的主要内容,如果未能解决你的问题,请参考以下文章