若是一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰以后,那么就具有了两层语义:html
在Java中long赋值不是原子操做,由于先写32位,再写后32位,分两步操做,而AtomicLong赋值是原子操做,为何?为何volatile能替代简单的锁,却不能保证原子性?这里面涉及volatile,是java中的一个我以为这个词在Java规范中从未被解释清楚的神奇关键词,在Sun的JDK官方文档是这样形容volatile的:java
The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes. A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable.c++
意思就是说,若是一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对全部线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在全部CPU可见。volatile彷佛是有时候能够代替简单的锁,彷佛加了volatile关键字就省掉了锁。但又说volatile不能保证原子性(java程序员很熟悉这句话:volatile仅仅用来保证该变量对全部线程的可见性,但不保证原子性)。这不是互相矛盾吗?程序员
volatile到底如何保证可见性和禁止指令重排序的。“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”(摘自《深刻理解Java虚拟机》)lock前缀指令实际上至关于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:缓存
例如你让一个volatile的integer自增(i++);安全
mov 0xc(%r10),%r8d ; Load(读取volatile变量值到local) inc %r8d ; Increment(增长变量的值) mov %r8d,0xc(%r10) ; Store(把local的值写回,,让其它的线程可见) lock addl $0x0,(%rsp) ; StoreLoad Barrier
内存屏障(memory barrier)是一个CPU指令。基本上,它是这样一条指令: a) 确保一些特定操做执行的顺序; b) 影响一些数据的可见性(多是某些指令执行后的结果)。编译器和CPU能够在保证输出结果同样的状况下对指令重排序,使性能获得优化。插入一个内存屏障,至关于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另外一个做用是强制更新一次不一样CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将获得最新值,而不用考虑究竟是被哪一个cpu核心或者哪颗CPU执行的。jvm
内存屏障(memory barrier)和volatile什么关系?上面的虚拟机指令里面有提到,若是你的字段是volatile,Java内存模型将在写操做后插入一个写屏障指令,在读操做前插入一个读屏障指令。这意味着若是你对一个volatile字段进行写操做,你必须知道:一、一旦你完成写入,任何访问这个字段的线程将会获得最新的值。二、在你写入前,会保证全部以前发生的事已经发生,而且任何更新过的数据值也是可见的,由于内存屏障会把以前的写入值都刷新到缓存。ide
volatile为何没有原子性?性能
明白了内存屏障(memory barrier)这个CPU指令,回到前面的JVM指令:从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在全部线程可见,也就是最后一步让全部的CPU内核都得到了最新的值,但中间的几步(从Load到Store)是不安全的,中间若是其余的CPU修改了值将会丢失。下面的测试代码能够实际测试voaltile的自增没有原子性:测试
假如线程1先执行,线程2后执行:
//线程1 boolean stop = false; while(!stop){ doSomething(); } //线程2 stop = true;
这段代码是很典型的一段代码,不少人在中断线程时可能都会采用这种标记办法。可是事实上,这段代码会必定会将线程中断么?不必定,也许在大多数时候,这个代码可以把线程中断,可是也有可能会致使没法中断线程(虽然这个可能性很小,可是只要一旦发生这种状况就会形成死循环了)。这是由于每一个线程在运行过程当中都有本身的工做内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在本身的工做内存当中。那么当线程2更改了stop变量的值以后,可是还没来得及写入主存当中,线程2转去作其余事情了,那么线程1因为不知道线程2对stop变量的更改,所以还会一直循环下去。
若是使用volatile修饰,第一,会强制将修改的值当即写入主存;第二,当线程2进行修改时,会致使线程1的工做内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);第三,因为线程1的工做内存中缓存变量stop的缓存行无效,因此线程1再次读取变量stop的值时会去主存读取。那么在线程2修改stop值时(固然这里包括2个操做,修改线程2工做内存中的值,而后将修改后的值写入内存),会使得线程1的工做内存中缓存变量stop的缓存行无效,而后线程1读取时,发现本身的缓存行无效,它会等待缓存行对应的主存地址被更新以后,而后去对应的主存读取最新的值。
public class Test { public volatile int inc = 0; public void increase() { inc++; } public static void main(String[] args) { final Test test = new Test(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保证前面的线程都执行完 Thread.yield(); System.out.println(test.inc); } }
程序的运行结果是一个小于10000的数字。volatile的可见性只能保证每次读取的是最新的值,可是volatile没办法保证对变量的操做的原子性。在前面已经提到过,自增操做是不具有原子性的,它包括读取变量的原始值、进行加1操做、写入工做内存。那么就是说自增操做的三个子操做可能会分割开执行,就有可能致使下面这种状况出现:
假如某个时刻变量inc的值为10,线程1对变量进行自增操做,线程1先读取了变量inc的原始值,而后线程1被阻塞了;而后线程2对变量进行自增操做,线程2也去读取变量inc的原始值,因为线程1只是对变量inc进行读取操做,而没有对变量进行修改操做,因此不会致使线程2的工做内存中缓存变量inc的缓存行无效,因此线程2会直接去主存读取inc的值,发现inc的值时10,而后进行加1操做,并把11写入工做内存,最后写入主存。而后线程1接着进行加1操做,因为已经读取了inc的值,注意此时在线程1的工做内存中inc的值仍然为10(由于线程1并无再次读取Iinc的值,而可见性只能保证每次读取的是最新的值),因此线程1对inc进行加1操做后inc的值为11,而后将11写入工做内存,最后写入主存。那么两个线程分别进行了一次自增操做后,inc只增长了1。
把上面的代码改为如下任何一种均可以达到效果:
采用synchronized:
public class Test { public int inc = 0; public synchronized void increase() { inc++; } public static void main(String[] args) { final Test test = new Test(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保证前面的线程都执行完 Thread.yield(); System.out.println(test.inc); } }
采用Lock:
public class Test { public int inc = 0; Lock lock = new ReentrantLock(); public void increase() { lock.lock(); try { inc++; } finally{ lock.unlock(); } } public static void main(String[] args) { final Test test = new Test(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保证前面的线程都执行完 Thread.yield(); System.out.println(test.inc); } }
采用AtomicInteger:
public class Test { public AtomicInteger inc = new AtomicInteger(); public void increase() { inc.getAndIncrement(); } public static void main(String[] args) { final Test test = new Test(); for(int i=0;i<10;i++){ new Thread(){ public void run() { for(int j=0;j<1000;j++) test.increase(); }; }.start(); } while(Thread.activeCount()>1) //保证前面的线程都执行完 Thread.yield(); System.out.println(test.inc); } }
在java 1.5的java.util.concurrent.atomic包下提供了一些原子操做类,即对基本数据类型的 自增(加1操做),自减(减1操做)、以及加法操做(加一个数),减法操做(减一个数)进行了封装,保证这些操做是原子性操做。atomic是利用CAS来实现原子性操做的(Compare And Swap),CAS其实是利用处理器提供的CMPXCHG指令实现的,而处理器执行CMPXCHG指令是一个原子性操做。
在前面提到volatile关键字能禁止指令重排序,因此volatile能在必定程度上保证有序性。volatile关键字禁止指令重排序有两层意思:
1)当程序执行到volatile变量的读操做或者写操做时,在其前面的操做的更改确定所有已经进行,且结果已经对后面的操做可见;在其后面的操做确定尚未进行;
2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
可能上面说的比较绕,举个简单的例子:
//x、y为非volatile变量 //flag为volatile变量 x = 2; //语句1 y = 0; //语句2 flag = true; //语句3 x = 4; //语句4 y = -1; //语句5
因为flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句一、语句2前面,也不会讲语句3放到语句四、语句5后面。可是要注意语句1和语句2的顺序、语句4和语句5的顺序是不做任何保证的。而且volatile关键字能保证,执行到语句3时,语句1和语句2一定是执行完毕了的,且语句1和语句2的执行结果对语句三、语句四、语句5是可见的。
那么咱们回到前面举的一个例子:
//线程1: context = loadContext(); //语句1 inited = true; //语句2 //线程2: while(!inited ){ sleep() } doSomethingwithconfig(context);
前面举这个例子的时候,提到有可能语句2会在语句1以前执行,那么久可能致使context还没被初始化,而线程2中就使用未初始化的context去进行操做,致使程序出错。这里若是用volatile关键字对inited变量进行修饰,就不会出现这种问题了,由于当执行到语句2时,一定能保证context已经初始化完毕。
volatile关键字在某些状况下性能要优于synchronized,可是要volatile关键字是没法替代synchronized关键字的,由于volatile关键字没法保证操做的原子性。一般来讲,不要将volatile用在getAndOperate场合(这种场不是原子,须要再加锁),仅仅set或者get的场景是适合volatile的。
下面列举几个Java中使用volatile的几个场景。
1.状态标记量
volatile boolean flag = false; while(!flag){ doSomething(); } public void setFlag() { flag = true; }
volatile boolean inited = false; //线程1: context = loadContext(); inited = true; //线程2: while(!inited ){ sleep() } doSomethingwithconfig(context);
2.double check
class Singleton{ private volatile static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if(instance==null) { synchronized (Singleton.class) { if(instance==null) instance = new Singleton(); } } return instance; } }