### 1.1內(nèi)存可見(jiàn)性
由于`Java`內(nèi)存模型(`JMM`)規(guī)定,所有的變量都存放在主內(nèi)存中,而每個(gè)線程都有著自己的工作內(nèi)存(高速內(nèi)存)。
線程在工作時(shí),需要將主內(nèi)存中的數(shù)據(jù)拷貝到工作內(nèi)存中。這樣對(duì)數(shù)據(jù)的任何操作都是基于工作內(nèi)存(效率提高),并且不能直接操作主內(nèi)存以及其他線程工作內(nèi)存中的數(shù)據(jù),之后再將更新之后的數(shù)據(jù)刷新到主內(nèi)存中。
> 這里所提到的主內(nèi)存可以簡(jiǎn)單的認(rèn)為是**堆內(nèi)存**,而工作內(nèi)存則可以認(rèn)為是**棧內(nèi)存**。
如圖所示:

所以在并發(fā)運(yùn)行時(shí)可能會(huì)出現(xiàn)線程B所讀取到的數(shù)據(jù)是線程A更新之前的數(shù)據(jù)。
顯然這肯定是會(huì)出問(wèn)題的,因此`volatile`的作用出現(xiàn)了:
> 當(dāng)一個(gè)變量被`volatile`修飾時(shí),任何線程對(duì)它的寫操作都會(huì)立即刷新到主內(nèi)存中,并且會(huì)強(qiáng)制讓緩存了該變量的線程中的數(shù)據(jù)清空,必須從主內(nèi)存重新讀取最新數(shù)據(jù)。
`volatile`修飾之后并不是讓線程直接從主內(nèi)存中獲取數(shù)據(jù),依然需要將變量拷貝到工作內(nèi)存中。
### 1.2內(nèi)存可見(jiàn)性的應(yīng)用
當(dāng)我們需要在兩個(gè)線程間依據(jù)主內(nèi)存通信時(shí),通信的那個(gè)變量就必須用`volatile`來(lái)修飾:
```java
public class Volatile implements Runnable{
? ? private static volatile boolean flag = true ;
? ? @Override
? ? public void run() {
? ? ? ? while (flag){
? ? ? ? }
? ? ? ? System.out.println(Thread.currentThread().getName() +"執(zhí)行完畢");
? ? }
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? Volatile aVolatile = new Volatile();
? ? ? ? new Thread(aVolatile,"thread A").start();
? ? ? ? System.out.println("main 線程正在運(yùn)行") ;
? ? ? ? Scanner sc = new Scanner(System.in);
? ? ? ? while(sc.hasNext()){
? ? ? ? ? ? String value = sc.next();
? ? ? ? ? ? if(value.equals("1")){
? ? ? ? ? ? ? ? new Thread(new Runnable() {
? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? ? ? ? ? aVolatile.stopThread();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }).start();
? ? ? ? ? ? ? ? break ;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? System.out.println("主線程退出了!");
? ? }
? ? private void stopThread(){
? ? ? ? flag = false ;
? ? }
}
```
主線程在修改了標(biāo)志位使得線程A立即停止,如果沒(méi)有用`volatile`修飾,就有可能出現(xiàn)延遲。
但這里有個(gè)無(wú)趣,這樣的使用方式容易給人的**感覺(jué)**是:~~對(duì)`volatile`修飾的變量進(jìn)行并發(fā)操作是線程安全的~~。
這里要重點(diǎn)強(qiáng)調(diào),**`volatile`并不能保證線程安全**!
如下程序:
```java
public class VolatileInc implements Runnable{
? ? private static volatile int count = 0 ; //使用 volatile 修飾基本數(shù)據(jù)內(nèi)存不能保證原子性
? ? //private static AtomicInteger count = new AtomicInteger() ;
? ? @Override
? ? public void run() {
? ? ? ? for (int i=0;i<10000 ;i++){
? ? ? ? ? ? count ++ ;
? ? ? ? ? ? //count.incrementAndGet() ;
? ? ? ? }
? ? }
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? VolatileInc volatileInc = new VolatileInc() ;
? ? ? ? Thread t1 = new Thread(volatileInc,"t1") ;
? ? ? ? Thread t2 = new Thread(volatileInc,"t2") ;
? ? ? ? t1.start();
? ? ? ? //t1.join();
? ? ? ? t2.start();
? ? ? ? //t2.join();
? ? ? ? for (int i=0;i<10000 ;i++){
? ? ? ? ? ? count ++ ;
? ? ? ? ? ? //count.incrementAndGet();
? ? ? ? }
? ? ? ? System.out.println("最終Count="+count);
? ? }
}
```
當(dāng)我們?nèi)齻€(gè)線程(t1,t2,main)同時(shí)對(duì)一個(gè)`int`進(jìn)行累加時(shí)會(huì)發(fā)現(xiàn)最終的值都會(huì)小于30000。
> 這是因?yàn)殡m然`volatile`保證了內(nèi)存可見(jiàn)性,每個(gè)線程拿到的值都是最新值,但`count++`這個(gè)操作并不是原子的,這里面涉及`獲取值`、`自增`、`賦值`的操作并不能同時(shí)完成。
所以想達(dá)到線程安全:
- 使這三個(gè)線程串行執(zhí)行(單線程,并沒(méi)有發(fā)揮多線程的優(yōu)勢(shì))。
- 使用`synchronized`或者鎖的方式來(lái)保證原子性。
- `Atomic`包中`AtomicInteger`來(lái)替換`int`,它利用了`CAS`指令來(lái)保證了原子性。
### 1.3指令重排
內(nèi)存可見(jiàn)只是`volatile`的其中一個(gè)語(yǔ)義,它還可以防止`JVM`進(jìn)行指令重排優(yōu)化。
```java
int a=10 ;//1
int b=20 ;//2
int c= a+b ;//3
```
理想情況下它的執(zhí)行順序是:`1 > 2 > 3`。但是有一種可能經(jīng)過(guò)JVM優(yōu)化之后代碼順序變?yōu)閌2 > 1 > 3`。
可以發(fā)現(xiàn)不管JVM怎么優(yōu)化,前提都是保證單線程中最終結(jié)果不變的情況。這里可能還看不出什么問(wèn)題??聪乱欢蝹未a。
```java
private static Map<String,String> value ;
private static volatile boolean flag = fasle ;
//以下方法發(fā)生在線程 A 中 初始化 Map
public void initMap(){
? ? //耗時(shí)操作
? ? value = getMapValue() ;//1
? ? flag = true ;//2
}
//發(fā)生在線程 B中 等到 Map 初始化成功進(jìn)行其他操作
public void doSomeThing(){
? ? while(!flag){
? ? ? ? sleep() ;
? ? }
? ? //dosomething
? ? doSomeThing(value);
}
```
這里就能看出問(wèn)題了,當(dāng)`flag`沒(méi)有被`volatile`修飾時(shí),`JVM`對(duì)1和2進(jìn)行重排,導(dǎo)致`value`都還沒(méi)有被初始化就有可能被線程B使用了。所以加上`volatile`之后可以防止這樣的重排優(yōu)化,保證業(yè)務(wù)的正確性。
**指令重排的應(yīng)用經(jīng)典場(chǎng)景(雙重懶加載單例)**
```java
public class Singleton {
? ? private static volatile Singleton singleton;
? ? private Singleton() {
? ? }
? ? public static Singleton getInstance() {
? ? ? ? if (singleton == null) {
? ? ? ? ? ? synchronized (Singleton.class) {
? ? ? ? ? ? ? ? if (singleton == null) {
? ? ? ? ? ? ? ? ? ? //防止指令重排
? ? ? ? ? ? ? ? ? ? singleton = new Singleton();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return singleton;
? ? }
}
```
這里的`volatile`關(guān)鍵字主要為了防止指令重排。
如果不用,`singleton = new Singleton();`,這段代碼其實(shí)分為三步:
1. 分配內(nèi)存空間。
2. 初始化對(duì)象。
3. 將`singleton`對(duì)象指向分配的內(nèi)存地址。
加上`volatile`是為了保證以上操作順序執(zhí)行,反之有可能第二步在第三步之間被執(zhí)行就有可能某個(gè)線程拿到的單例對(duì)象是還沒(méi)初始化的,以至于報(bào)錯(cuò)。
### 總結(jié)
`volatile`在Java并發(fā)中用的很多,比如像`Atomic`包中的`value`、以及`AbstractQueuedLongSynchronizer` 中的`state`都是被定義為`volatile`來(lái)用于保證內(nèi)存可見(jiàn)性。