volatile深刻理解

参考网页

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编程

volatile存在的意义——一些背景知识

共享变量--能够被多个线程访问的变量

能够被多个线程访问的变量称为共享变量。缓存

多线程场景下讨论共享变量才有意义。多线程

多线程场景能够是多CPU,也能够是单个CPU,只很少单个CPU是以线程调度的形式执行的多线程。并发

CPU高速缓存的引入

【CPU从内存读取数据的速度】和【CPU向内存写入数据速度】比【CPU执行指令的速度】要慢不少,所以若是任什么时候候CPU对数据的操做都要经过和内存的交互来进行,会大大下降CPU指令执行的速度。所以在CPU里面就有了【高速缓存】。性能

程序运行时,会将运算须要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就能够直接从它的高速缓存读取数据和向其中写入数据,当运算结束以后,再将高速缓存中的数据刷新到主存当中。优化

volatile存在的意义——并发场景(多线程场景)下的问题

缓存一致性问题

缓存一致性问题的产生--【多线程】场景下的【共享变量】存储在【CPU高速缓存】,【由于各个线程的操做致使各个共享变量副本不一致】,这就是缓存一致性问题

共享变量在多个CPU的高速缓存中存在副本(通常在多线程编程时才会出现),多个线程同一时间段内对共享变量进行操做,就可能存在缓存一致性问题。spa

缓存一致性问题的解决方案

一般来讲有如下2种解决方法:

1)总线加LOCK#锁

2)缓存一致性协议

这2种方式都是硬件层面上提供的方式。

CPU指令重排序

单线程下保证重排序后的最终执行结果和代码顺序执行的结果是一致的

通常来讲,处理器为了提升程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行前后顺序同代码中的顺序一致,可是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

重排序时会考虑指令之间的数据依赖性

处理器在进行重排序时会考虑指令之间的数据依赖性,若是一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2以前执行。

指令重排序不会影响单个线程的执行,可是会影响到并发线程执行的正确性

CPU指令重排序产生问题的缘由--【多线程场景】下【对共享变量操做】的协调

硬件层面上CPU为了提高自身执行性能对指令进行了优化和重排序。

多线程场景下CPU指令重排序可能会影响并发程序的执行,致使问题的根本缘由仍是【多线程场景】下【对共享变量操做】的协调上产生了问题。如何协调好?这个要依据具体的业务场景。volatile能够禁止指令重排序,在须要禁止指令重排序时可使用volatile。

说明volatile,为啥要提【缓存一致性问题】和【CPU指令重排序】?

【缓存一致性问题】和【CPU指令重排序】实际上对应的是并发编程中的【可见性】和【有序性】的问题。后面能够看到,volatile实际上就能够解决并发编程时【可见性】和【有序性】的问题。

后面会讲到,并发编程涉及【原子性】、【可见性】和【有序性】三方面的问题,只有三方面的问题都能解决,才能保证并发程序正确执行。

volatile能够解决【可见性】和【有序性】的问题,可是没法解决【原子性】的问题。volatile能解决的问题也决定了volatile的应用场景。

并发编程须要知足操做的原子性、可见性和有序性【都是为了正确操做共享变量】

原子性

就是要保证对一个事务要进行【完整的】、【不可中断】的操做。

何为事务?事务包括一个操做或者多个操做。一个事务所包含的全部操做,要么所有执行而且执行的过程不会被任何因素打断,要么就都不执行。

可见性

多个线程的共享变量。当一个线程对共享变量修改时,其余线程可以当即看获得修改的值。

缓存一致性问题就致使共享变量的可见性出现问题。也就是说多CPU下各个CPU高速缓存中共享变量的副本不一致,就会致使各个CPU见到的共享变量不一致,这样共享变量的可见性就出了问题。

有序性

有序性:即程序执行的顺序按照代码的前后顺序执行。

由于CPU会对指令进行重排序,因此最终的指令执行顺序在CPU硬件层面不必定严格按照java代码层面的顺序执行。

考虑单线程和多线程两种情形。单线程下CPU指令重排序可是仍能保证指令逻辑顺序的正确性。

可是多线程下,因为共享变量的存在,可能致使不可预知的、不可预期的、引发混乱的问题。

这就要求程序员理解这些,并采用技术手段保证多线程下程序仍可按照正确的逻辑运行。

★总结【并发程序正确执行的条件】

要想并发程序正确地执行,必需要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会致使并发程序运行不正确。

volatile关键字--保证可见性和有序性,可是没法保证原子性

volatile关键字的两层语义:简单来讲就是volatile保证了可见性和有序性

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰以后,那么就具有了两层语义:

1)保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的。

2)禁止进行指令重排序。

volatile保证原子性吗

http://www.javashuo.com/article/p-glkohnan-dq.html

volatile能保证有序性吗--内存屏障,禁止指令重排序

在前面提到volatile关键字能禁止指令重排序,因此volatile能在必定程度上保证有序性。

volatile关键字禁止指令重排序有两层意思:

1)当程序执行到volatile变量的读操做或者写操做时,在其前面的操做的更改确定所有已经进行,且结果已经对后面的操做可见;在其后面的操做确定尚未进行;

2)在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

★volatile的原理和实现机制

前面讲述了源于volatile关键字的一些使用,下面咱们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。

下面这段话摘自《深刻理解Java虚拟机》:

“观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

lock前缀指令实际上至关于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

1)它确保指令重排序时不会把其后面的指令排到内存屏障以前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操做已经所有完成;禁止指令重排序

2)它会强制将对缓存的修改操做当即写入主存

3)若是是写操做,它会致使其余CPU中对应的缓存行无效

volatile的使用

使用volatile必须具有如下2个条件

http://www.javashuo.com/article/p-bdtfcxtz-du.html

volatile使用场景

状态标记量

Double check

http://www.javashuo.com/article/p-pkrakqzd-dw.html

volatile与synchronized使用比较

http://www.javashuo.com/article/p-ejcmrhac-mn.html

相关文章
相关标签/搜索