volatile 的作用请参阅
http://lizhe.name.csdn.net/node/94
关于CAS
http://lizhe.name.csdn.net/node/96
本文讨论volatile和CAS因false sharing的原因对性能造成的影响
先看下面的例子
package testatomic;
public class Data {
public volatile long value1;
public volatile long value2;
public volatile long value3;
public volatile long value4;
}
package testatomic;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TestAtomic3 extends Thread{
public static void main(String args[]) throws InterruptedException{
Data data = new Data();
ExecutorService es = Executors.newFixedThreadPool(4);
long start = System.currentTimeMillis();
int loopcont = 1000000000;
Thread t[] = new Thread[4];
t[0] = new Thread( () -> {
for(int i=0;i<loopcont;i++){
data.value1 = data.value1+i;
}
} );
t[1] = new Thread( () -> {
for(int i=0;i<loopcont;i++){
data.value2 = data.value2+i;
}
} );
t[2] = new Thread( () -> {
for(int i=0;i<loopcont;i++){
data.value3 = data.value3+i;
}
} );
t[3] = new Thread( () -> {
for(int i=0;i<loopcont;i++){
data.value4 = data.value4+i;
}
} );
for(Thread item:t){
es.submit(item);
}
for(Thread item:t){
item.join();
}
es.shutdown();
es.awaitTermination(9999999, TimeUnit.SECONDS);
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
线程数 | 使用Volatile | 不使用volatile |
1 | 23346 | 1284 |
4 | 57429 | 673 |
我们一个一个来解释测试结果
单线程 时, 由于需要实时回写主内存, 无法使用高速缓存, 所以使用volatile比不使用volatile耗时要长一些
多线程时, volatile这种实时回写主内存的方式也会带来不小的性能消耗
不使用volatile时, 单线程的耗时要比4线程长
这些都是正常的, 但是如果你注意到在使用volatile时, 4个线程反而比单线程耗时更长,这就奇怪了,多线程反而更慢
这是为什么呢? 罪魁祸首其实是cpu伪共享 (false sharing)
jvm会尽可能的开辟连续内存空间用来存储Data类中的4个long型数据, 也就是说在物理内存上它们是连续的
当value1 被加载进内存之后, cpu会将value2,value3,value4一同加载进cpu的高速缓存中,构成一个缓存行,比如下图中的变量x和y,这样做的意义在于加载了value1的代码很可能也需要访问其他变量

这种加载方式有助于提高高速缓存的命中率, 不过这种方式也有缺点,每次程序更新了本地缓存(比如这里的L3级缓存)中的某个值, 当前内核必需通知其他内核重新加载其缓存行
比如上图中内核1修改x之后,内核2需要重新加载该缓存行,内核2修改了y,内核1也需要重新加载缓存行,正如我们上面的代码例子中的value1,value2,value3…
本质上伪共享与volatile没有必要联系,但是为什么这里我们只有使用了volatile才会对性能造成影响呢
因为如果变量不是volatile的, jvm会将这些值存储在线程栈中,不会及时回写给主内存(不会触及L3寄存器), 各个线程之间也就不会互相干扰