java volatile CAS 性能影响 (伪共享)

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
1233461284
457429673

我们一个一个来解释测试结果

单线程 时, 由于需要实时回写主内存, 无法使用高速缓存, 所以使用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寄存器), 各个线程之间也就不会互相干扰