volatile的两个功能:①防止指令重排②可见性。
1什么是指令重排?
为了使处理器内部的运算单元可以充分的被利用,处理器可能会对输入代码进行乱序执行优化,处理器会在计算之后将乱序的结果重组,保证该结果和顺序执行的结果一致,但并不保证程序中各个语句计算的先后顺序和输入代码中的顺序一致。Java虚拟机的即时编译器也有类似的指令重排的机制。
而volatile防止指令重排的功能,防止多线程情况下发生一些我们意想不到的事情。
2那么什么是可见性呢?
对于多线程的情况下,如何实现一个共享变量对于多线程可见。
我们需要理解Java内存模型JMM.
对于Java内存模型来说:
Java内存模型的主要目标是定义了程序变量中各个变量的访问的规则,。这里的变量是值多线程共享的变量,包括实例字段,静态字段和数组。不包括局部变量和方法参数,因为他们是线程私有的。
Java内存模型规定所有的变量都存储在主内存中。每个线程都有自己的工作内存。线程的工作内存保存了被该线程使用到的变量的在主内存拷贝的副本。线程的读取修改变量都必须在工作内存中进行,不能在主内存中直接读取和修改变量。不同的线程也无法访问到其他线程的工作内存。而且,为效率,Java内存模型允许编译器进行代码的顺序调整。
在JVM中,主内存就对应着Java堆。工作内存就对应着Java栈。
所以对于多线程来说,如果一个线程通过工作线程中共享变量的值,那么如果其他线程对这个共享变量进行修改,那么这个线程是不知道的,就会产生多线程中数据不一致的问题。
而volatile的语义就是保证这个变量对于所有线程的可见性,这里的可见性是指当一条线程修改了这个变量的值,新值对于其他的线程是可以立即知道的。
volatile对于多线程可见性的原理:
Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值来实现可见性的。(就是每次使用前从主内存刷新)。
对于多处理器来说,如果共享变量加了volatile关键字,那么这个共享变量进行写操作的时候就会多出第二行代码,简单来说就是多了lock前缀,这个前缀在多核处理器下就会发生两件事:
①将当前处理器缓存行的数据写回到系统内存。
②这个写回内存的操作会使其他在cpu缓存了该内存地址的数据无效。
为了提高速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存后再操作。但操作完并不知道何时写回内存,但是对于volatile关键字的变量进行写操作,JVM就会向处理器发送一条lock前缀指令,将这个变量所在的缓存行的数据写回到系统内存。但是就算写回到系统内存,其他处理器的缓存行缓存的值是新的还是旧的。但是在多处理器下,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己的数据是否过期了。如果发现过期了,当处理器需要对这个变量进行操作的时候,再从系统内存把这个数读到处理器缓存里。
问题:既然volatile保证了共享变量在多线程的可见性,那么适用了这个关键字的共享变量就是线程安全的吗?
答案:不是。
Java里面的运算并不是原子操作。所以它并不是线程安全的。
参考:Java并发编程艺术
深入理解Java虚拟机