volatile 和 atomic 原子性的区别和联系
作者:wenyinfeng
转载时,请注明原文出处: http://blog.sina.com.cn/s/blog_c00b6f6201016mbk.html
谢谢!
An incorrect piece of lore that is often repeated in Java threading discussions is, "Atomic operations do not need to be synchronized." An atomic operation is one that cannot be interrupted by the thread scheduler; if the operation begins, then it will run to completion before the possibility of a context switch.
Atomicity applies to "simple operations" on primitive types except for longs and doubles. Reading and writing primitive variables other than long and double is guaranteed to go to and from memory as indivisible (atomic) operations. However, the JVM is allowed to
perform reads and writes of 64- bit quantities (long and double variables) as two separate 32-bit operations, raising the possibility that a context switch could happen in the middle of a
read or write, and then different tasks could see incorrect results (this is sometimes called word tearing, because you might see the value after only part of it has been changed). However, you do get atomicity (for simple assignments and returns) if you use the volatile keyword when defining a long or double variable (note that volatile was not working properly before Java SE5). Different JVMs are free to provide stronger guarantees, but you should not rely on platform-specific features.
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.
原子操作就是不能被线程调度机制中断的操作。不正确的认识:原子操作不需要进行同步。
在Java 中除了 long 和 double 之外的所有基本类型的读和赋值,都是原子性操作。而64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性,会产生字撕裂问题。但是当你定义long或double变量时,如果使用 volatile关键字,就会获到(简单的赋值与返回操作的)原子性(注意,在Java SE5之前,volatile一直不能正确的工作)。见第四版《Thinking in java》第21章并发。
volatile关键字确保了应用中的可视性。如果你将一个域声明为volatile,那么只要这个域产生了写操作,那么所有的读操作就都可以看到这个修改。
下面来看看volatile 和原子性的区别和联系。我将从下面几个问题进行思考和探索。
第1个问题:如何证明 作者上面所说的long 和 double 的简单操作是非原子性的 - 会产生字撕裂问题,而使用volatile 关键字可以保证 long 或 double 的简单操作具有原子性,以及验证其它的基本类型(如int)的简单操作具有原子性。
我的思路:
1. 多个任务对同一个 long 变量进行赋值修改,所赋的值为从 1 到64位 仅有1位为1,其余位均为0的数,并所返回赋值完成后的值。如果long 变量不具有原子性,那么很有可能得到一个多个位为1的数或者所有位为0的数,一旦发生,我们输出一条信息,并终止程序。
2. 如果1出现字撕裂,那么long 变量加上 volatile 限制后,赋值返回的数应该都满足从 1 到64位仅有1位为1,其余位均为0,即不会出现字撕裂。
3. 同理,测试int变量,但是由于int 赋值具有原子性,所以即使不加 volatile 限制,赋值返回的数应该都满足从 1 到64位仅有1位为1,其余位均为0。
具体见下面我写的测试代码
// 证明 long 变量简单操作(赋值和返回)不具有原子性,存在字撕裂问题。验证 volatile 可确保
// long 变量简单操作具有原子性。验证 int 变量简单操作(赋值和返回)具有原子性
package concurrency;
import java.util.concurrent.*;
class Operation{
private int num = 0;
private long bigNum = 0;
public int assignInt(int n){
num = n;
Thread.yield();
return num;
}
public long assignLong(long n){
bigNum = n;
Thread.yield();
return bigNum;
}
}
public class AtomicTest{
static class IntOperationTask implements Runnable{
private Operation operation;
public IntOperationTask(Operation op){
operation = op;
}
public void run() {
while(true){
int oldNum, newNum;
for(int i = 0; i < 32; i++){
oldNum = 1 << i;
newNum = operation.assignInt(oldNum);
if(oldNum != newNum){
int bits = 0;
for(int j = 0; j < 32; j++){
if(0 != (newNum & (1 << j)))