java语言提供了一种稍弱的内存同步机制,即volatile变量。用来确保将变量的更新操做通知到其它线程,保证了新值能当即同步到主内存,以及每次使用前当即从内存刷新。当变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的。java
volatile变量对全部线程可见,对volatile变量全部的写操做都能当即反应到其它的线程中,可是volatile并不能保证操做的原子性,所以volatile变量的运算并非线程安全的。编程
在并发编程中,咱们一般会遇到如下三个问题:原子问题,可见性问题,有序性问题。缓存
即一个操做或多个操做要么所有执行而且执行过程当中不能被任何操做打断,要么都不执行。只有简单的读取、赋值(并且必须是将数字赋值给某个变量,变量 之间的赋值不是原子操做)才是原子操做。java内存模型只保证了基本读取和赋值是原子操做,若是要实现更大范围操做的原子性,能够经过 synchronized和lock来实现。因为synchronized和lock能保证任一时刻只有一个线程执行该程序块,那么天然就不存在原子性问 题了,从而保证了原子性。安全
可见性是指多个线程访问同一个变量时,一个线程修改了这个变量的值,其它线程可以当即看到修改的值。对于可见性,java提供了volatile关 键字来保证可见性。当一个共享变量被volatile修饰的时候,它会保证修改的值当即被更新到内存,当有其它线程须要读取时,它会去内存中去读新值。而 普通共享变量不能保证可见性。由于普通共享变量被修改后,何时被写入内存是不肯定的。当其余线程去读取新值时,此时内存中可能仍是本来的值,所以没法 保证可见性。多线程
即程序执行的顺序按照代码的前后顺序执行。在java内存模型中,容许编译器和处理器对执行进行重排,可是重排过程不会影响单线程程序的执行,却会 影响到多线程并发执行的正确性。在java中,能够经过volatile关键字来保证必定的有序性。另外能够经过synchronized和lock来保 证有序性。很显然,synchronized和lock保证了每一个时刻只有一个线程执行同步代码,至关因而让线程执行同步代码,天然保证了有序性。并发
通常来讲,处理器为了提升程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行顺序同代码中的顺序一致,可是它会保证程序最终执 行结果和代码顺序执行的结果是一致的。若是两个语句之间没有数据依赖,那么可能会被重排。指令重排不会影响单个线程的执行,可是会影响到线程并发执行的正 确性。性能
在并发编程中,若是想要一个程序正确的执行,必须保证原子性、可见性以及有序性,只要有一个没有被保证,就有可能会致使程序运行不正确。优化
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰以后,那么就具有了两层含义:线程
1)保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这个新值对其它线程来讲是当即可见的。内存
2)禁止指令重排。
volatile关键字可以保证可见性,可是不能保证原子性。
volatile能禁止指令重排,因此volatile能在必定程度上保证有序性。
1.当程序执行到volatile变量的读操做或者写操做时,在其前面的操做的更改确定所有已经执行结束,而且结果已经对后面的修改可见,其后的操做必定尚未进行。
2.在进行指令优化时,不会对volatile变量进行重排。
从volatile关键字生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令。
lock前缀指令实际上至关于一个内存屏障。内存屏障会提供三个功能:
1)它确保指令重排时不会把其后面的指令排到内存屏障以前的位置,也不会把前面的指令排到内存屏障以后,即在执行到内存屏障这句指令时,在它前面的操做已经所有完成。
2)它会强制对缓存的修改操做当即写入内存。
3)若是是写操做,它会致使其余cpu中对应的缓存行无效。
synchronized关键字是防止多个线程同时执行一段代码,那么就会影响到程序的执行效率,而volatile关键字在某些状况下性能要优于 synchronized,可是要注意volatile关键字没法保证操做的原子性。一般来讲,使用volatile必须具有如下两个条件。
1)对变量的写操做不依赖于当前值。
2)该变量没有包含在具备其它变量的不变式中。
意思就是保证操做是原子操做,才能保证使用volatile关键字的程序在并发时可以正确执行。