public class Demo09 { public static boolean flag = true; public static class T1 extends Thread { public T1(String name) { super(name); } @Override public void run() { System.out.println("線程" + this.getName() + " in"); while (flag) { ; } System.out.println("線程" + this.getName() + "中止了"); } } public static void main(String[] args) throws InterruptedException { new T1("t1").start(); //休眠1秒 Thread.sleep(1000); //將flag置爲false flag = false; } }
運行上面代碼,會發現程序沒法終止。java
線程t1的run()方法中有個循環,經過flag來控制循環是否結束,主線程中休眠了1秒,將flag置爲false,按說此時線程t1會檢測到flag爲false,打印「線程t1中止了」,爲什麼和咱們指望的結果不同呢?運行上面的代碼咱們能夠判斷,t1中看到的flag一直爲ture,主線程將flag置爲false以後,t1線程中沒有看到,因此一直死循環。程序員
那麼t1中爲何看不到被主線程修改以後的flag?緩存
要解釋這個,咱們須要先了解一下java內存模型(JMM),Java線程之間的通訊由Java內存模型(本文簡稱爲JMM)控制,JMM決定一個線程對共享變量的寫入什麼時候對另外一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(main memory)中,每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化。Java內存模型的抽象示意圖以下:多線程
從上圖中能夠看出,線程A須要和線程B通訊,必需要經歷下面2個步驟:併發
下面經過示意圖來講明這兩個步驟:ide
如上圖所示,本地內存A和B有主內存中共享變量x的副本。假設初始時,這三個內存中的x值都爲0。線程A在執行時,把更新後的x值(假設值爲1)臨時存放在本身的本地內存A中。當線程A和線程B須要通訊時,線程A首先會把本身本地內存中修改後的x值刷新到主內存中,此時主內存中的x值變爲了1。隨後,線程B到主內存中去讀取線程A更新後的x值,此時線程B的本地內存的x值也變爲了1。
從總體來看,這兩個步驟實質上是線程A在向線程B發送消息,並且這個通訊過程必需要通過主內存。JMM經過控制主內存與每一個線程的本地內存之間的交互,來爲java程序員提供內存可見性保證。高併發
對JMM瞭解以後,咱們再看看文章開頭的問題,線程t1中爲什麼看不到被主線程修改成false的flag的值,有兩種可能:優化
對於上面2種狀況,有沒有什麼辦法能夠解決?this
是否有這樣的方法:線程中修改了工做內存中的副本以後,當即將其刷新到主內存;工做內存中每次讀取共享變量時,都去主內存中從新讀取,而後拷貝到工做內存。線程
java幫咱們提供了這樣的方法,使用volatile修飾共享變量,就能夠達到上面的效果,被volatile修改的變量有如下特色:
咱們修改一下開頭的示例代碼:
public volatile static boolean flag = true;
使用volatile修飾flag變量,而後運行一下程序,輸出:
線程t1 in 線程t1中止了
這下程序能夠正常中止了。
volatile解決了共享變量在多線程中可見性的問題,可見性是指一個線程對共享變量的修改,對於另外一個線程來講是不是能夠看到的。
java高併發系列交流羣