关于volatile和同步相关的东西,网上有太多错误和解释不清的东西, 因此查阅相关书籍和文章后总结以下, 若是仍是也存在不正确的内容,请必定要指出来, 以避免误人子弟:)html
1. 原子性与可视性java
原子性是指操做不能被线程调度机制中断, 除long和double以外的全部基本类型的读或写操做都是原子操做,注意这里说的读写, 仅指如return i, i = 10, 对于像i++这种操做,包含了读,加1,写指令,因此不是原子操做。 对于long和double的读写,在64位JVM上会把它们看成两个32位来操做,因此不具有原子性。数组
在定义long和double类型变量时,若是使用volatile来修饰,那么也能够得到原子性,除此之外,volatile与原子性没有直接关系。缓存
可视性,volatile的主要做用就是确保可视性,那么什么是可视性?
在系统中(多处理器更加明显),对某一变量的修改有时会暂时保存在本地处理器的缓存中,尚未写入共享内存,这时候有另一个线程读取变量在共享内存的值,那么这个修改对这个线程就是不可视的。
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。并且,当成员变量发生变化时,强迫线程将变化值直接写到共享内存。这样在任什么时候刻,两个不一样的线程老是看到某个成员变量的同一个值。 原子操做不必定就有可视性, 好比赋值,i = 10, 若是i没有被特别修饰, 那么由于缓存的缘由, 它仍然多是不可视的安全
因此原子性和可视性是彻底不一样的两个概念多线程
2. volatile的应用场景
详细能够参考java语言架构师Brain Geotz的文章
Java 中volatile 变量能够被看做是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 优势是所需的编码较少,而且运行时开销也较少, 不会引发线程阻塞。Volatile 变量具备 synchronized 的可视性特性,可是不具有原子特性。
这就致使Volatile 变量可用于提供线程安全,可是应用场景很是有限,在一些经典java书里,基本都不推荐使用volatile替代synchronized来实现同步,由于风险较大, 很容易出错。
Brain给出的使用volatile实现线程安全的条件:
对变量的写操做不依赖于当前值。 (count++这种就不行了)
该变量没有包含在具备其余变量的不变式中(Invariants,例如 “start <=end”)。架构
个人理解是, 这两个条件都是由于volatile不能提供原子性致使的, 若是多线程执行的一个操做不是原子性的, 使用volatile时就必定要慎重。
若是知足这两个条件, 多线程执行的操做是原子性的, 那就是可使用,如:
将 volatile 变量做为状态标志使用并发
volatile boolean shutdownRequested; . ... public void shutdown() { shutdownRequested = true; } //不依赖当前值,原子操做 public void doWork() { while (!shutdownRequested) { // do stuff } }
文章中的其余几种模式, 也都差很少这个意思。ide
还有一种状况,若是读操做远远超过写操做,能够结合使用内部锁和 volatile 变量来减小公共代码路径的开销。
结合使用 volatile 和 synchronized 实现 “开销较低的读-写锁”测试
@ThreadSafe public class CheesyCounter { // Employs the cheap read-write lock trick // All mutative operations MUST be done with the 'this' lock held @GuardedBy("this") private volatile int value; public int getValue() { return value; } //使用volatile替代synchronized public synchronized int increment() { return value++; }
然而,你能够在读操做中使用 volatile 确保当前值的可见性,所以可使用锁进行全部变化的操做,使用 volatile 进行只读操做。其中,锁一次只容许一个线程访问值,volatile 容许多个线程执行读操做,所以当使用 volatile 保证读代码路径时,要比使用锁执行所有代码路径得到更高的共享度 —— 就像读-写操做同样。
3. synchronized
class AtomTest implements Runnable { private volatile int i = 0; public int getVal() {return i;} public synchronized void inc() {i++; i++;} @Override public void run() { while (true) { inc(); } } } public class TestThread { public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); AtomTest at = new AtomTest(); exec.execute(at); while (true) { int val = at.getVal(); if (val % 2 != 0) { System.out.println(val); System.exit(0); } } } }
结果会输出奇数, 退出程序, 缘由是getVal读到了inc的中间值。 这种状况只能在getVal方法前加synchronized
在读取的时候也加锁, 这样在读的时候若是正在写, 那么等待, 因此就不会读到inc的中间值。
关于synchronized值得注意的几个点:
1) 全部对象都含有一个锁,当调用到synchronized(object)块时,先检测obj有没有加锁,若是有, 阻塞, 若是没有, 对object加锁, 执行完后释放锁。
2) synchronized void f() {//...} 等价于 void f() { synchronized(this) {//...} }, 在当前对象上加锁
3) synchronized 提供原子性和可视性, 被它彻底保护的变量不须要用volatile
4) synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类须要你显式的指定它的某个方法为synchronized方法。
注意第一点, 很是重要:虽然sync块能够包裹一段代码,可是锁是加对象上,不是加在代码上,它的工做机制以下:
对于synchronized(obj) {//...}: 先检测obj有没有加锁,若是有, 阻塞, 若是没有, 对obj加锁, 执行块中的代码,完毕后释放锁。这里只检测obj对象上的锁,不关注代码块里的代码或者对象。
因此, 加锁的范围由obj决定,理解了这一点, 下面的不少种状况就会很容易理解:
1. 当两个并发线程访问同一个对象object中的这个相同synchronized(this)同步代码块时,一个时间内针对该对象的操做只能有一个线程获得执行。另外一个线程必须等待。
- 若是同一对象已经加锁, 另外一线程执行到sync块,检测到有锁挂起。
2. 然而,另外一个线程仍然能够访问该object中的非synchronized(this)同步代码块。
- 非sync代码,不关注对象是否加锁
3. 当一个线程访问object的一个synchronized(this)同步代码块时,其余线程对该object中全部其它synchronized(this)同步代码块的访问将被阻塞。
- synchronized(this) 只是关注this对象, 只要this已加锁,执行到同一对象中不一样方法的sync块时,也会阻塞。
4. 不一样的对象实例的synchronized(this)方法是不相干扰的。也就是说,其它线程照样能够同时访问相同类的另外一个对象实例中的synchronized方法。
- 能够同时访问不一样对象中的sync块, 缘由很简单, 由于synchronized(this),关注的是this对象,不一样对象的this是不同的。
5. 同理,也能够对其余对象加锁,
1) 对于类中的成员对象: private Integer i = new Integer(0); synchronized(i) {//...} i建立在堆上,每一个对象有一个i,因此效果与synchronized(this)同样。
2) 对于静态成员对象: private static Integer i = new Integer(0); synchronized(i) {//...}: i建立在静态区,属于类, 因此效果与synchronized(ClassName.class)同样。
6.对类对象加锁时,对该类的全部对象都起做用:synchronized(ClassName.class) {//...}
最后我在测试代码时,发现对于private Integer i = 0; synchronized(i) {//...}
锁的效果是全局的,推测多是Integer对0进程打包时,自动生成的这个对象可能在常量区。 后来查了下资料才发现并不是如此:
Integer实现中有一个IntegerCache类,它包含一个静态的Integer数组,在类加载时就将-128 到 127 的Integer对象建立了,并保存在cache数组中,一旦程序调用valueOf 方法,若是i的值是在-128 到 127 之间就直接在cache缓存数组中去取Integer对象。 因此这里的i引用的是整个全局数组里值, 因此锁也是全局的了。。。
若是改为private Integer i = 300, 而后加锁就只在本对象有效了。 缘由是i不在缓存范围,因此建立在了堆上。
refer http://blog.csdn.net/xiaohai0504/article/details/6885137