volatile的定义

Java语言规范第3版中对volatile的定义如下:Java编程语言允许线程访问共享变量,为了
确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言
提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存
模型确保所有线程看到这个变量的值是一致的。

volatile的可见性

我们都知道volatile可以保证可见性和禁止指令重排,那么volatile是如何保证可见性的呢
volatile修饰的变量经过汇编得到的指令如下:

0x01a3de1d: movb $0×0,0×1104800(%esi);
0x01a3de24: lock addl $0×0,(%esp);   //lock前缀指令

有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码,那么这行代码做了什么事情呢?

1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效

为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据 以64字节为单位(一个缓存行) 读到内部
缓存(L1,L2或其他)后再进行操作,但是操作完后不知道何时会写到内存中。
如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据
写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操
作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一
致性协议
,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当
处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状
态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存
里。
volatile的内幕_cpu

volatile的两条实现原则

  • Lock前缀指令会引起处理器缓存回写到内存
    在多处理器环境中,LOCK#信号确保在声言该信号期间,处理器可以独占任何共享内存 (锁总线) 。
    但是,就目前的处理器而言,LOCK#信号一般不锁总线,而是锁缓存,毕竟锁总线开销的比较大。

如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反,它会锁定这块内存区
域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为“缓存锁定”

  • 一个处理器的缓存回写到内存会导致其他处理器的缓存无效
    Intel 64处理器使用MESI(修改、独占、共享、无效)控制协议去维护内部缓存和其他处理器缓存的一致
    性。在多核处理器系统中进行操作的时候,。处理器使用嗅探技术保证它的内部缓存、系统内存和其他处理器的
    缓存的数据在总线上保持一致。例如,如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充。

volatile的内幕_cpu_02
现在处理器的L1、L2或L3缓存的高速缓存行是64个字节宽,不支持部分填充缓存行。所以在操作多个共享变量时,如果缓存行是64字节宽,且共享变量频繁的写,那么我们想办法把单个共享变量所在的内存大小变成64字节,也就是一个共享变量为一个缓存行,这样来提高系统的性能,如上图,如果有两个共享变量,X和Y。如果在同一个缓存行,A处理器操作X,B处理器操作Y,由于缓存一致性协议,A处理器操作之后,嗅探B处理器,此时B所加载的缓存行就会失效,待A处理器写回内存时,B处理器重新加载,可见,这样的效率不高,所以每个共享变量为一个缓存行,这样就避免了缓存行失效的情况。

public class T {

    public static void main(String[] args) throws InterruptedException {
        B1[] b = new B1[2];
        b[0] = new B1();
        b[1] = new B1();
        Thread t1 = new Thread(() -> {
            for (int i = 0;i< 1000_0000;i++){
               b[0].count++;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0;i< 1000_0000;i++){
                b[1].count++;
            }
        });

        long start = System.nanoTime();
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        long end = System.nanoTime();
        System.out.println(end - start);
    }


    static class A {
        private long a1,a2,a3,a4,a5,a6,a7; //这里占据了56字节
    }

    static class B extends A{
        private volatile long count = 0; //这里总共便有64字节
    }

    static class B1 {
        private volatile long count = 0;
    }
}