一、volatile的做用html
一個線程共享變量(類的成員變量、類的靜態成員變量等)被volatile修飾以後,就具備如下做用:java
1)併發中的變量可見性(不一樣線程對該變量進行操做時的可見性),即一個線程修改了某個變量的值,則該新值對其餘線程當即可見(可當即訪問新值/當即強制寫入主存);編程
2)禁止指令重排(包括java編譯器和CPU運行時指令重排序);緩存
3)禁用緩存(java虛擬機規範)---子線程的工做內存(包括了CPU緩存)。多線程
二、相關概念併發
2.1)指令重排序:eclipse
(1)java編譯器運行時指令重排序(實質是JVM的優化處理),java源碼編譯生成class文件後,JVM須要在運行時(runtime)將字節碼文件(class文件)轉化爲操做系統可以執行的指令(JIT編譯器),在轉換的過程當中jvm會對指令進行優化調整,以提升運行效率。jvm
(2)CPU運行時指令重排序,cpu優化的方式,爲避免處理器訪問主內存的時間開銷,處理器採用緩存機制(三層緩存)提升性能(緩存之間的數據一致性遵循協議規範),當CPU寫緩存時,發現緩存區塊正被其餘CPU佔用,爲了提升CPU的處理性能,可能將後面的讀緩存命令優先執行。ide
2.2)java內存模型規範:性能
線程要操做共享變量時,須要從主內存讀取到工做內存,改變值後須要從工做內存同步到主內存中。多線程的狀況下,同步到主內存時遵循同步協議規範。
三、相關現象分析
3.1)先看一段代碼,
public class VolatileInfo { private static boolean flag = true; public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); long end = 0; int index = 0; while (VolatileInfo.flag) { index++; end = System.currentTimeMillis(); if ((end-start)/1000==5) {//5秒以後結束任務 break; } } System.out.println("index:"+index); System.out.println("cost:"+(end-start)/1000+"s"); } }).start(); try { TimeUnit.SECONDS.sleep(2);//阻塞線程2秒 } catch (InterruptedException e) { e.printStackTrace(); } VolatileInfo.flag = false; System.out.println("條件置爲false"); } }
輸出結果:
條件置爲false index:269460217 cost:5s
耗時5秒,並非2秒後中止(注:靜態變量flag用volatile 修飾後執行時間是2秒)。
3.2)問題分析:
1)主線程阻塞後修改的flag的值並無及時寫入到主存,子線程沒有及時讀取到flag值,致使循環繼續執行---即存在緩存;
2)指令重排序,先從主內存中執行讀的操做,陷入死循環,再也不向主內存寫入。
3.3)解決辦法:
1)flag變量使用volatile關鍵詞修飾;
2)使用Synchronized加同步鎖(從主存中直接讀取,至關於禁用緩存,參考4.3);
while (VolatileInfo.flag) { synchronized (this) { index++; } end = System.currentTimeMillis(); if ((end - start) / 1000 == 5) {// 5秒以後結束任務 break; } }
3)從eclipse中將jvm執行class文件的方式改成client(默認是server模式,jvm進行了一些優化調整);
4)從eclipse中配置添加關閉server優化的參數---此處請自行百度^_^; ---------- 禁止指令重排。
四、擴展知識點
4.1)java內存模型多線程狀況下的工做內存和主內存同步協議的8種原子操做:
lock(鎖定):做用於主內存,將主內存中的變量鎖定,爲一個線程所獨有;
unlock(解鎖):做用於主內存,解除lock的鎖定,釋放後的變量可以被其餘線程訪問;
read(讀取):做用於主內存,將主內存中的變量讀取到工做內存中,以便隨後的load動做使用;
load(載入):做用於工做內存,它把read讀取的值保存到工做內存中的變量副本中;
use(使用):做用於工做內存,它把工做內存中的值傳遞給線程代碼執行引擎;
assign(賦值):做用於工做內存,它把從執行引擎處理返回的值從新賦值給工做內存中的變量;
store(存儲):做用於工做內存,將變量副本中的值傳送到主內存中,以備隨後的write操做使用;
write(寫入):做用於主內存,它把store傳送值寫到主內存的共享變量中。
4.2)java內存模型操做規範:
1)將一個變量從主內存複製到工做內存要順序依次(不必定連續)執行read、load操做;
2)作了assign操做,必須同步回主內存等。
4.3)保證線程共享變量可見性的方式:
1)用final修飾的變量
2) Synchronized 同步鎖
Synchronized規範, 進入同步塊前,先清空工做內存中的共享變量,從主內存中從新加載; 解鎖前必須把修改的共享變量同步回主內存。
鎖機制,鎖機制保護共享資源,只有得到鎖的線程才能操做共享資源。
3) 用volatile修飾的變量
volatile語義規範,使用volatile修飾的變量時,必須從主內存中加載,而且read、load是連續的;修改volatile修飾的變量時,必須當即同步到主內存,而且store、write是連續的。
一字一句敲的,支持原創,轉載請註明出處,謝謝:http://www.javashuo.com/article/p-rtleuiaf-bm.html