java併發編程 volatile關鍵字 精準理解

一、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

相關文章
相關標籤/搜索