Java執行緒面試題(03) Java中的volatile如何工作? Java中的volatile關鍵字示例

劉近光發表於2017-11-27

本文為本博主翻譯,未經允許,嚴禁轉載!!!

簡介

什麼是Java中的volatile變數以及何時使用Java中的volatile變數是一道Java面試中經常被問及的多執行緒訪問問題。儘管許多程式設計師知道什麼是volatile變數,但是他們在回答第二部分問題什麼地方使用volatile變數時失敗,因為大部分人對Java中volatile變數並沒有一個清晰的理解和應用。在本教程中,我們將通過提供一個Java中volatile變數的簡單示例來解決這個問題,並討論在Java中使用volatile變數的一些情況。無論如何,Java中的volatile關鍵字被用作Java編譯器和Thread的指示器:它不會快取這個變數的值,並且總是從主記憶體中讀取它。所以如果你想共享任何變數,其中讀寫操作是通過實現原子的。比如讀取和寫入一個int或一個布林變數,你可以宣告他們作為volatile變數。

Java 5除了自動裝箱,列舉,泛型和可變引數等主要變化外,Java引入了一些Java記憶體模型(JMM)的變化,它保證了一個執行緒的變好對另一個執行緒的可視性,也被解釋為“發生之前”來解決記憶體的寫入問題。在一個執行緒中發生的記憶體寫入會“洩漏”,並被另一個執行緒看到。
Java volatile關鍵字不能與方法或類一起使用,只能與變數一起使用。 Java可變關鍵字還保證可見性和排序,Java 5之後, 寫入任何volatile變數發生在任何讀取到volatile變數之前。順便說一句,使用volatile關鍵字還可以防止編譯器或JVM對程式碼進行重新排序或將其從同步障礙中移走。

Java中的volatile變數示例

為了理解java中的volatile關鍵字的例子,我們回到Java中的Singleton模式。

/**
 * Java program to demonstrate where to use Volatile keyword in Java. 
 * In this example Singleton Instance is declared as volatile variable to ensure 
 * every thread see updated value for _instance. 
 * @author Javin Paul
 */
public class Singleton {
	private static volatile Singleton _instance;

	// volatile variable
	public static Singleton getInstance() {
		if (_instance == null) {
			synchronized (Singleton.class) {
				if (_instance == null)
					_instance = new Singleton();
			}
		}
		return _instance;
	}
}
如果仔細檢視程式碼,您將能夠弄清楚:
1)我們只是一次建立例項
2)在第一次請求來的時候建立例項。


如果我們不定義_instance為volatile變數,則建立Singleton的例項的Thread不能與其它執行緒通訊,告知該例項已經被建立直到它退出Singleton同步塊,所以如果執行緒A正在建立Singleton例項並且隨後執行緒切換,所有其他執行緒將不能將_instance的值看作非null,並且它們將認為它仍然為空。

為什麼?因為讀執行緒沒有進行任何鎖定,直到寫執行緒退出同步塊,記憶體將不會同步,_instance的值不會在主記憶體中更新。在Java中使用Volatile關鍵字時,這是由Java自己處理的,所有讀者執行緒都可以看到這些更新。

因此,除了Java中的同步關鍵字文章中相關總結外,volatile關鍵字還用於線上程之間傳遞記憶體內容。

我們來看看Java中volatile關鍵字的另一個例子

在寫遊戲的大部分時間裡,我們使用一個變數bExit來檢查使用者是否按下了退出按鈕,這個變數的值在事件執行緒中被更新,並在遊戲執行緒中被檢查過,所以如果我們沒有在這個變數中使用volatile關鍵字,Game Thread可能會錯過來自事件處理程式執行緒的更新,如果它在Java中不同步。 Java中的volatile關鍵字保證volatile變數的值總是從主記憶體中讀取,而Java Memory模型中的“happen-before”關係將確保記憶體的內容將被傳送到不同的執行緒。

private boolean bExit;

while(!bExit) {
   checkUserPosition();
   updateUserPosition();
}
在這個程式碼示例中,一個執行緒(遊戲執行緒)可以快取“bExit”的值,而不是每次從主記憶體獲取它,如果在任何其他執行緒(事件處理執行緒)之間改變值;它不會被這個執行緒看到。在java中使布林變數“bExit”變成volatile可以確保不會發生這種情況。
為了真正理解這個複雜的概念, 我還建議您閱讀Brian Goetz撰寫的Java Concurrency in Practice書中關於volatile變數的主題。

何時在Java中使用volatile變數

在volatile關鍵字的學習中最重要的一點是瞭解何時在Java中使用volatile變數。許多程式設計師知道什麼是volatile變數,它是如何工作的,但他們從來沒有真正使用volatile來實現任何實際目的。這裡有幾個示例演示何時在Java中使用Volatile關鍵字:
1)如果你想以原子方式讀寫long型別變數和double型別變數,可以使用Volatile變數。 long和double都是64位資料型別,預設情況下,long和double的寫入不是原子和平臺依賴。許多平臺在長和雙變數2步執行寫操作,在每一步寫32位,由於這可能使一個執行緒看到從兩個不同的寫32位。您可以通過在Java中使用long和double變數volatile來避免此問題。
2)在某些情況下,volatile可以用作實現Java同步的一種替代方法,如Visibility。用volatile變數,保證所有讀寫器執行緒在寫操作完成後都會看到volatile變數的更新值,而不用volatile關鍵字,不同的讀執行緒可能會看到不同的值。
3)volatile變數可用於通知編譯器一個特定的欄位被多個執行緒訪問,這將阻止編譯器進行任何重新排序或在多執行緒環境中不希望的任何型別的優化。如果沒有volatile變數,編譯器可以重新排序程式碼,可以自由地快取volatile變數的值,而不是總是從主記憶體中讀取資料。就像下面沒有volatile變數的例子可能會導致無限迴圈

private boolean isActive = thread; 
public void printMessage(){ 
    while(isActive) { 
	    System.out.println("Thread is Active"); 
    } 
} 
如果沒有volatile修飾符,則不能保證一個執行緒從其他執行緒看到isActive的更新值。編譯器還可以自由快取isActive的值,而不是在每次迭代中從主儲存器中讀取。通過使isActive成為一個volatile變數,可以避免這些問題。

4)另一個可以使用volatile變數的地方是在Singleton模式下修復雙重檢查的鎖定。

Java中的Volatile關鍵字的重點

1)Java中的volatile關鍵字只適用於一個變數,使用volatile關鍵字和class和method是非法的。
2)Java中的volatile關鍵字保證volatile變數的值總是從主記憶體中讀取,而不是從Thread的本地快取中讀取。
3)在Java中,對於使用Java volatile關鍵字(包括長變數和雙變數)宣告的所有變數,讀寫操作是原子操作。
4)在變數Java中使用volatile關鍵字可降低記憶體一致性錯誤的風險,因為在Java中對volatile變數的任何寫入都會與隨後的同一變數的讀取之間建立一個happen-before關係。
5)從Java 5,volatile變數的更改對其他執行緒始終可見。更重要的是,這也意味著當一個執行緒在Java中讀取一個volatile變數時,它不僅會看到volatile變數的最新變化,而且還會看到導致變化的程式碼的副作用。
6)即使沒有在Java中使用volatile關鍵字,讀取和寫入也是原子參考變數是大多數原始變數(除了long和double之外的所有型別)。
7)對Java中的volatile變數的訪問從來沒有機會阻止,因為我們只是做一個簡單的讀或寫操作,所以不像一個同步塊,我們永遠不會持有任何鎖或等待任何鎖。
8)作為物件引用的Java volatile變數可能為空。
9)Java volatile關鍵字並不意味著原子化,它在宣告volatile之後會是原子的常見誤解,為了使操作原子化,還需要使用Java中的同步方法或塊來確保獨佔訪問。
10).如果多個執行緒之間不共享變數,則不需要在該變數中使用volatile關鍵字。

Java中的synchronized和volatile關鍵字之間的區別

volatile和synchronized之間的區別, 是另一個流行的關於多執行緒和併發訪問的核心Java面試問題。記住volatile不是synchronized關鍵字的替代品,但可以在某些情況下用作替代品。 這裡列出了一些Java中volatile和synchronized關鍵字之間的差異:
1) volatile關鍵字是一個欄位修飾符,synchronized修改程式碼塊和方法。
2) synchronized獲取並釋放監視器的鎖, volatile關鍵字不需要這個鎖。
3)Java中的執行緒可以被阻塞,以便在同步的情況下等待任何監視器,這與Java中的volatile關鍵字不同。
4)在Java中,synchronized方法相比volatile關鍵字, 會影響效能
5) 由於Java中的volatile關鍵字只同步執行緒記憶體和“主”記憶體之間的一個變數的值,synchronized同步執行緒記憶體和“主”記憶體之間的所有變數的值,並鎖定和釋放監視器。由於這個原因,Java中的synchronized關鍵字可能比volatile更具開銷。
6)您不能同步空物件,但Java中的volatile變數可能為空。
7) 從Java 5寫入volatile欄位與監視器釋放具有相同的記憶效應,並且從易失性欄位讀取具有與監視器獲取相同的記憶效應

簡而言之,Java中的volatile關鍵字不是synchronized塊或方法的替代,但在某些情況下非常方便,可以節省Java中使用同步帶來的效能開銷。如果你想了解更多關於易失性的內容,我也建議在Java記憶體模型的常見問題上進行解釋。

原文連結

How Volatile in Java works? Example of volatile keyword in Java



相關文章