系列文章傳送門:html
Java多線程學習(二)synchronized關鍵字(1)程序員
java多線程學習(二)synchronized關鍵字(2) 面試
Java多線程學習(四)等待/通知(wait/notify)機制微信
系列文章將被優先更新於微信公衆號<font color="red">「Java面試通關手冊」</font>,歡迎廣大Java程序員和愛好技術的人員關注。ide
本節思惟導圖:
思惟導圖源文件+思惟導圖軟件關注微信公衆號:「Java面試通關手冊」回覆關鍵字:「Java多線程」免費領取。
在程序設計中,尤爲是在C語言、C++、C#和Java語言中,使用volatile關鍵字聲明的變量或對象一般具備與優化、多線程相關的特殊屬性。一般,volatile關鍵字用來阻止(僞)編譯器認爲的沒法「被代碼自己」改變的代碼(變量/對象)進行優化。如在C語言中,<font color="red">volatile關鍵字能夠用來提醒編譯器它後面所定義的變量隨時有可能改變,所以編譯後的程序每次須要存儲或讀取這個變量的時候,都會直接從變量地址中讀取數。若是沒有volatile關鍵字,則編譯器可能優化讀取和存儲,可能暫時使用寄存器中的值,若是這個變量由別的程序更新了的話,將出現不一致的現象。據</font>
在C環境中,volatile關鍵字的真實定義和適用範圍常常被誤解。雖然C++、C#和Java都保留了C中的volatile關鍵字,但在這些編程語言中volatile的用法和語義卻截然不同。
在 JDK1.2 以前,Java的內存模型實現老是從<font color="red">主存(即共享內存)讀取變量</font>,是不須要進行特別的注意的。而在當前的 Java 內存模型下,線程能夠把變量保存<font color="red">本地內存</font>(好比機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能形成一個線程在主存中修改了一個變量的值,而另一個線程還繼續使用它在寄存器中的變量值的拷貝,形成<font color="red">數據的不一致</font>。
要解決這個問題,就須要把變量聲明爲<font color="red"> volatile</font>,這就指示 JVM,這個變量是不穩定的,每次使用它都到主存中進行讀取。
volatile 修飾的成員變量在每次被線程訪問時,都強迫從主存(共享內存)中重讀該成員變量的值。並且,當成員變量發生變化時,強迫線程將變化值回寫到主存(共享內存)。這樣在任什麼時候刻,兩個不一樣的線程老是看到某個成員變量的同一個值,這樣也就保證了同步數據的可見性。
<font size="2">RunThread.java</font>
private boolean isRunning = true; int m; public boolean isRunning() { return isRunning; } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } @Override public void run() { System.out.println("進入run了"); while (isRunning == true) { int a=2; int b=3; int c=a+b; m=c; } System.out.println(m); System.out.println("線程被中止了!"); } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) throws InterruptedException { RunThread thread = new RunThread(); thread.start(); Thread.sleep(1000); thread.setRunning(false); System.out.println("已經賦值爲false"); } }
<font size="2">運行結果:</font>
RunThread類中的isRunning變量沒有加上<font color="red">volatile關鍵字</font>時,運行以上代碼會出現<font color="red">死循環</font>,這是由於isRunning變量雖然被修改可是沒有被寫到<font color="red">主存</font>中,這也就致使該線程在<font color="red">本地內存</font>中的值一直爲true,這樣就致使了死循環的產生。
解決辦法也很簡單:isRunning變量前加上<font color="red">volatile關鍵字</font>便可。
這樣運行就不會出現死循環了。
<font size="2">加上volatile關鍵字後的運行結果:</font>
<font color="red">你是否是覺得到這就完了?</font>
不存在的!!!(這裏還有一點須要強調,下面的內容必定要看,否則你在用volatile關鍵字時會很迷糊,由於書籍幾乎都沒有提這個問題)
假如你把while循環代碼里加上任意一個輸出語句或者sleep方法你會發現死循環也會中止,無論isRunning變量是否被加上了上volatile關鍵字。
<font size="2">加上輸出語句:</font>
while (isRunning == true) { int a=2; int b=3; int c=a+b; m=c; System.out.println(m); }
<font size="2">加上sleep方法:</font>
while (isRunning == true) { int a=2; int b=3; int c=a+b; m=c; try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
<font color="red">這是爲何呢?</font>
<font color="red">由於:JVM會盡力保證內存的可見性,即使這個變量沒有加同步關鍵字</font>。換句話說,只要CPU有時間,JVM會盡力去保證變量值的更新。這種與volatile關鍵字的不一樣在於,volatile關鍵字會強制的保證線程的可見性。而不加這個關鍵字,JVM也會盡力去保證可見性,可是若是CPU一直有其餘的事情在處理,它也沒辦法。最開始的代碼,一直處於死循環中,CPU處於一直佔用的狀態,這個時候CPU沒有時間,JVM也不能強制要求CPU分點時間去取最新的變量值。而<font color="red">加了輸出或者sleep語句以後,CPU就有可能有時間去保證內存的可見性,因而while循環能夠被終止</font>。
《Java併發編程藝術》這本書上說保證可是在自增操做(非原子操做)上不保證,《Java多線程編程核心藝術》這本書說不保證。
我我的更傾向於這種說法:<font color="red">volatile沒法保證對變量原子性的</font>。我我的感受《Java併發編程藝術》這本書上說volatile關鍵字保證原子性嗎可是在自增操做(非原子操做)上不保證這種說法是有問題的。只是我的見解,但願不要被噴。能夠看下面測試代碼:
<font size="2">MyThread.java</font>
public class MyThread extends Thread { volatile public static int count; private static void addCount() { for (int i = 0; i < 100; i++) { count=i; } System.out.println("count=" + count); } @Override public void run() { addCount(); } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) { MyThread[] mythreadArray = new MyThread[100]; for (int i = 0; i < 100; i++) { mythreadArray[i] = new MyThread(); } for (int i = 0; i < 100; i++) { mythreadArray[i].start(); } } }
<font size="2">運行結果:</font>
上面的「count=i;」是一個原子操做,可是運行結果大部分都是正確結果99,可是也有部分不是99的結果。
<font color="red">解決辦法:</font>
<font color="red">使用synchronized關鍵字加鎖</font>。(這只是一種方法,Lock和AtomicInteger原子類均可以,由於以前學過synchronized關鍵字,因此咱們使用synchronized關鍵字的方法)
修改MyThread.java以下:
public class MyThread extends Thread { public static int count; synchronized private static void addCount() { for (int i = 0; i < 100; i++) { count=i; } System.out.println("count=" + count); } @Override public void run() { addCount(); } }
這樣運行輸出的count就都爲99了,因此<font color="red">要保證數據的原子性仍是要使用synchronized關鍵字</font>。
參考:
《Java多線程編程核心技術》
《Java併發編程的藝術》
極客學院Java併發編程wiki: http://wiki.jikexueyuan.com/p...
若是你以爲博主的文章不錯,歡迎轉發點贊。你能從中學到知識就是我最大的幸運。
歡迎關注個人微信公衆號:「Java面試通關手冊」(分享各類Java學習資源,面試題,以及企業級Java實戰項目回覆關鍵字免費領取)。另外我建立了一個Java學習交流羣(羣號:174594747),歡迎你們加入一塊兒學習,這裏更有面試,學習視頻等資源的分享。