http://www.javashuo.com/article/p-hsugcpqq-hw.htmlhtml
https://www.ibm.com/developerworks/cn/java/j-jtp06197.htmljava
https://blog.csdn.net/dl88250/article/details/5439024程序员
http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization编程
能够被多个线程访问的变量称为共享变量。缓存
多线程场景下讨论共享变量才有意义。多线程
多线程场景能够是多CPU,也能够是单个CPU,只很少单个CPU是以线程调度的形式执行的多线程。并发
【CPU从内存读取数据的速度】和【CPU向内存写入数据速度】比【CPU执行指令的速度】要慢不少,所以若是任什么时候候CPU对数据的操做都要经过和内存的交互来进行,会大大下降CPU指令执行的速度。所以在CPU里面就有了【高速缓存】。性能
程序运行时,会将运算须要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就能够直接从它的高速缓存读取数据和向其中写入数据,当运算结束以后,再将高速缓存中的数据刷新到主存当中。优化
共享变量在多个CPU的高速缓存中存在副本(通常在多线程编程时才会出现),多个线程同一时间段内对共享变量进行操做,就可能存在缓存一致性问题。spa
一般来讲有如下2种解决方法:
1)总线加LOCK#锁
2)缓存一致性协议
这2种方式都是硬件层面上提供的方式。
通常来讲,处理器为了提升程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行前后顺序同代码中的顺序一致,可是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
处理器在进行重排序时会考虑指令之间的数据依赖性,若是一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2以前执行。
硬件层面上CPU为了提高自身执行性能对指令进行了优化和重排序。
多线程场景下CPU指令重排序可能会影响并发程序的执行,致使问题的根本缘由仍是【多线程场景】下【对共享变量操做】的协调上产生了问题。如何协调好?这个要依据具体的业务场景。volatile能够禁止指令重排序,在须要禁止指令重排序时可使用volatile。
【缓存一致性问题】和【CPU指令重排序】实际上对应的是并发编程中的【可见性】和【有序性】的问题。后面能够看到,volatile实际上就能够解决并发编程时【可见性】和【有序性】的问题。
后面会讲到,并发编程涉及【原子性】、【可见性】和【有序性】三方面的问题,只有三方面的问题都能解决,才能保证并发程序正确执行。
volatile能够解决【可见性】和【有序性】的问题,可是没法解决【原子性】的问题。volatile能解决的问题也决定了volatile的应用场景。
就是要保证对一个事务要进行【完整的】、【不可中断】的操做。
何为事务?事务包括一个操做或者多个操做。一个事务所包含的全部操做,要么所有执行而且执行的过程不会被任何因素打断,要么就都不执行。
多个线程的共享变量。当一个线程对共享变量修改时,其余线程可以当即看获得修改的值。
缓存一致性问题就致使共享变量的可见性出现问题。也就是说多CPU下各个CPU高速缓存中共享变量的副本不一致,就会致使各个CPU见到的共享变量不一致,这样共享变量的可见性就出了问题。
有序性:即程序执行的顺序按照代码的前后顺序执行。
由于CPU会对指令进行重排序,因此最终的指令执行顺序在CPU硬件层面不必定严格按照java代码层面的顺序执行。
考虑单线程和多线程两种情形。单线程下CPU指令重排序可是仍能保证指令逻辑顺序的正确性。
可是多线程下,因为共享变量的存在,可能致使不可预知的、不可预期的、引发混乱的问题。
这就要求程序员理解这些,并采用技术手段保证多线程下程序仍可按照正确的逻辑运行。
要想并发程序正确地执行,必需要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会致使并发程序运行不正确。
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰以后,那么就具有了两层语义:
1)保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的。
2)禁止进行指令重排序。
http://www.javashuo.com/article/p-glkohnan-dq.html
在前面提到volatile关键字能禁止指令重排序,因此volatile能在必定程度上保证有序性。
volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操做或者写操做时,在其前面的操做的更改确定所有已经进行,且结果已经对后面的操做可见;在其后面的操做确定尚未进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
前面讲述了源于volatile关键字的一些使用,下面咱们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。
下面这段话摘自《深刻理解Java虚拟机》:
“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”
lock前缀指令实际上至关于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:
1)它确保指令重排序时不会把其后面的指令排到内存屏障以前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操做已经所有完成;禁止指令重排序
2)它会强制将对缓存的修改操做当即写入主存;
3)若是是写操做,它会致使其余CPU中对应的缓存行无效。
http://www.javashuo.com/article/p-bdtfcxtz-du.html
http://www.javashuo.com/article/p-pkrakqzd-dw.html