1. 程式人生 > 程式設計 >Java併發程式設計——volatile關鍵字

Java併發程式設計——volatile關鍵字

一、volatile是什麼

volatile是Java併發程式設計中重要的一個關鍵字,被比喻為“輕量級的synchronized”,與synchronized不同的是,volatile只能修飾變數,無法修飾方法及程式碼塊等。
下面是使用volatile關鍵字實現的單例模式:

public class Singleton implements Serializable {
  private static volatile Singleton singleton;
  private Singleton() {}
  public static Singleton getSingleton() {
    if (singleton==null) {         // 1
      synchronized (Singleton.class) {  // 2
        if (singleton==null) {     // 3
          singleton = new Singleton();// 4
        }
      }
    }
    return singleton;
  }
  private Object readResolve() { //防止序列化破壞單例模式
    return singleton;
  }
}

1.單例為什麼使用volatile關鍵字?

首先要理解new Singleton()做了什麼。1.看class物件是否載入,如果沒有就進行類的載入、解析和初始化;2.虛擬機器分配記憶體空間,初始化例項,3.呼叫建構函式,4.返回地址給引用。而cpu為了優化程式,可能會進行指令重排序,導致例項記憶體還沒分配,就被使用了。

假設有兩個執行緒A和B,執行緒A執行到new Singleton(),開始初始化例項物件,由於存在指令重排序,這次new操作,先把引用賦值了,還沒有執行建構函式(沒有真正執行完)。這時時間片結束了,切換到執行緒B執行,執行緒B呼叫new Singleton()方法,發現引用不等於null,就直接返回引用地址了,然後執行緒B執行了一些操作,就可能導致執行緒B使用了還沒被初始化的變數。

2.單例模式中步驟1、2、3、4存在的意義何在?

首先,步驟2、3是保證單例。假設執行緒A和B都執行到了步驟2,執行緒A拿到了鎖,執行步驟3,如果此時沒有建立例項,執行緒A會執行new建立例項,然後執行緒A釋放鎖,執行緒B拿到鎖,首先執行步驟3,發現已經建立了例項,直接返回。加鎖是比較消耗資源的,步驟1就是為了減少資源的消耗。

二、volatile的特性

1.禁止指令重排序

指令重排序是JVM為了優化指令、提高程式執行效率,在不影響單執行緒程式執行結果的前提下,儘可能地提高並行度。指令重排序包括編譯器重排序和執行時重排序。

volatile關鍵字提供記憶體屏障的方式來防止指令被重排,編譯器在生成位元組碼檔案時,會在指令序列中插入記憶體屏障來禁止特定型別的處理器重排序。

JVM記憶體屏障插入策略:

  • 每個volatile寫操作的前面插入一個StoreStore屏障,Store1;StoreStore;Store2,在Store2及後續的寫入操作執行前,保證Store1的寫入操作對其他處理器可見,保證了有序性和可見性;
  • 在每個volatile寫操作的後面插入一個StoreLoad屏障,Store1;StoreLoad;Load2,在Load2及後續的讀取操作執行前,保證Store1的寫入操作對其他處理器可見,它的開銷是最大的,兼具其他三種的作用,保證了有序性和可見性;
  • 在每個volatile讀操作的後面插入一個LoadLoad屏障,Load1;LoadLoad;Load2,在Load2及後續的讀取操作執行前,保證Load1讀取的資料已經讀取完畢;
  • 在每個volatile讀操作的後面插入一個LoadStore屏障,Load1;LoadStore;Store2,在Store2及後續的寫入操作執行前,保證Load1讀取的資料已經讀取完畢。

2.保證記憶體可見性

可見性是指對volatile變數的讀總能獲取其他任意執行緒對volatile變數的最後的寫。
可見性的實現基於volatile讀寫的記憶體語義:

  • volatile寫的記憶體語義:當寫入一個volatile變數時,JVM將執行緒工作記憶體中的變數值重新整理到主記憶體中;
  • volatile讀的記憶體語義:當讀取一個volatile變數時,JVM首先將改工作記憶體中的變數設定為無效,重新從主記憶體中獲取最新的有效值。

三、使用場景

(1)volatile是輕量級同步機制。與synchronized的區別是volatile只能保證有序性和可見性,不能保證原子性。
(2)volatile不能修飾寫入操作依賴當前值的變數。宣告為volatile的簡單變數如果當前值與該變數以前的值相關,那麼volatile關鍵字不起作用,也就是說如下的表示式都不是原子操作:“count++”、“count = count+1”。
(3)當要訪問的變數已在synchronized程式碼塊中,或為常量時,沒必要使用volatile;
(4)volatile保證了有序性,遮蔽掉了JVM中必要的程式碼優化,所以在效率上比較低,因此一定在必要時才使用此關鍵字。
(5)在以下兩個場景中可以使用volatile來代替synchronized:

  • 運算結果不依賴變數的當前值,或者能夠確保只有單一的執行緒會修改變數的值。
  • 變數不需要與其他狀態變數共同參與不變約束。

以上就是淺析Java併發程式設計——volatile關鍵字的詳細內容,更多關於Java併發程式設計——volatile關鍵字的資料請關注我們其它相關文章!