我尝试使用'count'作为volatile运行以下代码:
ExecutorService e = Executors.newFixedThreadPool(2);
for (int i=0; i<2; i++)
{
e.execute(new Runnable(){
public void run() {
for (int i=0; i<1000000; i++)
count++;
}
}
);
}
e.shutdown();
e.awaitTermination(1, TimeUnit.DAYS);
System.out.println(count);
Run Code Online (Sandbox Code Playgroud)
计数通常最终不到1,000,000.
我正在使用x86处理器 - 英特尔酷睿2双核E8400,热点1.6.24.为了实现原子更新,与volatile运算符一起使用的++运算符通常会丢失更新参数,如下所示:线程1和2都为v读取值0,两者都将其递增1并写入值1.
在x86上使用volatile时,这个论点似乎分崩离析,因为:
1)每次访问volatile变量都会通过CPU缓存层次结构.只有当JVM能够证明单个线程访问volatile变量时,JVM才能生成汇编代码以多次访问volatile,而不会从内存中加载/存储,这不是这里的情况.
2)只有一个CPU可以在修改状态特定的缓存行,所以如果两个CPU试图增加V,只有一个会在获得含钒进入修改状态的高速缓存行成功.另一个将使其高速缓存行无效,并且稍后将进入修改状态,其高速缓存包含正确的值1并将该变量更新为2.
我在这里错过了什么?
你错过了++不是原子操作的事实.
如果您将代码重写为:
for (int i=0; i<1000000; i++)
int tmp = count;
tmp = tmp + 1;
count = tmp;
}
Run Code Online (Sandbox Code Playgroud)
这会让它更清楚吗?这里不需要内存模型或缓存行详细信息 - 我们需要的是两个线程,它们都读取相同的值,都进行独立工作,然后再次存储它们的计算值.