Java关键字volatile,原子性,变量可见性

  1. 内存模型与CPU缓存

本来CPU计算的数字都是从主从main memory中读取的,但是CPU运行的速度比计算机读取内存的速度快,为了补齐这个短板,所以出现了CPU缓从这种东西。

在多CPU系统(或多核处理器——一个芯片上有多个CPU),每个CPU有自己的缓存。两个线程A,B在不同的CPU上同时跑,A对主存的某个共享变量修改后会暂时存在CPU a的缓存中。线程B在CPU b上跑,B仍旧是从主存中读取该共享变量,此时B读到的就是旧值了。就出现了数据的不一致性。

这里出现不一致的条件:必须是多个线程并且访问共享变量,而不是普通变量。

 为了解决这个问题,有两种方式:

在总线上加LOCK#锁;

使用缓从一致性协议,比如MESI协议。

2. 并发环境下的可见性、原子性、有序性

原子性:一个操作,要么执行,要么不执行,在执行的过程中不会被打断。

JAVA原子性适用于除了long和double的原始数据类型的“简单操作”。从内存中读写除了long和double的原始数据类型是原子操作的。


简单操作指的是,简单的读取、赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)

i++            # java中不是原子的
i = i+1        # java中不是原子的
i = j          # java中不是原子的
i = 1          # java中是原子的
上面四个语句只有最后一个是原子的。这里注意:i=j,虽然不是原子的(有两步:先从内从读取 j 的值,再将 j 的值写入内从),但是这里的每一步是原子的(这两步都是简单的读写操作)。i++在C++里可能就是原子的,这个与C++本身的内存模型有关。

在32位上,对64bit的long和double变量的读写是分成两个32bit读写的,因此上下文切换可能发生在读(或写)进行到一半时,这叫做word tearing。

用violate定义long或者double变量时, 对 “简单的” 负值和return操作能保证原子性。

不同的JVM提供了对原子性不同程度的保证。。。


可见性:一个线程对共享变量的修改应该被应用内的其他线程立即可见。

有序性:程序执行的顺序按照代码的先后顺序执行。


这里只讨论原子性和有序性。


原子性、可见性

原子性与可见性是两个不同的概念!


Java中原子性保证:Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。


Java中可见性保证:synchronized和Lock、volatile三种。推荐synchronized方式,volatile有局限性,适合某个特定场合。


3. Java的volatile关键字

volatile 单词的意思:易变的,不稳定的,易挥发的。

下面的英文来自Thinking in Java , edtion 4》

volatile 含义:

The volatile keyword also ensures visibility across the application. If you declare a field to be volatile, this means that as soon as a write occurs for that field, all reads will see the change. This is true even if local caches are involved—volatile fields are immediately written through to main memory, and reads occur from main memory.

大意:可见性和原子性是不同的两个概念。对一个非volatile变量的原子操作的结果不会立即刷新到主从。多线程环境下对某个共享变量访问时,要么把这个变量定义成volatile的,要么使用synchronization,这样就保证了该变量的可见性。但如果该变量的操作是在synchronized方法或代码块中的话,就不用将该变量定义为volatile了,因为synchronization同步机制会强迫刷新到内存,强迫一个线程对共享变量做的改变对整个应用可见。
It’s important to understand that atomicity and volatility are distinct concepts. An atomic operation on a non-volatile field will not necessarily be flushed to main memory, and so another task that reads that field will not necessarily see the new value. If multiple tasks are accessing a field, that field should be volatile; otherwise, the field should only be accessed via synchronization. Synchronization also causes flushing to main memory, so if a field is completely guarded by synchronized methods or blocks, it is not necessary to make it volatile. 


volatile关键字的局限:
volatile doesn’t work when the value of a field depends on its previous value (such as incrementing a counter), nor does it work onfields whose values are constrained by the values of other fields, such as the lower and upper bound of a Range class which must obey the constraint lower <= upper. 
It’s typically only safe to use volatile instead of synchronized if the class has only one mutable field. Again, your first choice should be to use the synchronized keyword—that’s the safest approach, and trying to do anything else is risky.


If you define a variable as volatile, it tells the compiler not to do any optimizations that would remove reads and writes that keep the field in exact synchronization with the local data in the threads. In effect, reads and writes go directly to memory, and are not cached, volatile also restricts compiler reordering of accesses during optimization. However, volatile doesn’t affect the fact that an increment isn’t an atomic operation.


Basically, you should make a field volatile if that field could be simultaneously accessed by multiple tasks, and at least one of those accesses is a write. For example, a field that is used as a flag to stop a task must be declared volatile.


参考

http://www.cnblogs.com/dolphin0520/p/3920373.html

http://cmsblogs.com/?p=2092

阅读更多
文章标签: java
个人分类: Java
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭