Java Volatile關鍵字你真的瞭解嗎

正文

在談 Volatile 之前,我們先回顧下 Java 內存模型 的三要素:原子性、可見性、有序性,也就是大傢常提到的並發編程三要素。

並發編程的三要素

1.原子性

和數據庫事務中的原子性一樣,滿足原子性特性的操作是不可中斷的,要麼全部執行成功要麼全部執行失敗。

隻有簡單的讀取、賦值(而且必須是將數字賦值給某個變量,變量之間的相互賦值不是原子操作)才是原子操作。

比如:

i = 2;j = i;i++;i = i + 1;

上面4個操作中,i=2是讀取操作,必定是原子性操作,j=i你以為是原子性操作,其實吧,分為兩步,一是讀取i的值,然後再賦值給j,這就是2步操作瞭,稱不上原子操作,i++和i = i + 1其實是等效的,讀取i的值,加1,再寫回主存,那就是3步操作瞭。

所以上面的舉例中,最後的值可能出現多種情況,就是因為滿足不瞭原子性。

非原子操作都會存在線程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作,java的concurrent包下提供瞭一些原子類:比如:AtomicIntegerAtomicLong等。

2.可見性

多個線程訪問同一個共享變量時,其中一個線程對這個共享變量值的修改,其他線程能夠立刻獲得修改以後的值。

3.有序性

編譯器和處理器為瞭優化程序性能而對指令序列進行重排序,也就是你編寫的代碼順序和最終執行的指令順序是不一致的。但是重排序過程不會影響到單線程程序的執行,卻會影響到多線程並發執行的正確性。

Volatile

Volatile 是一個Java語言的類型修飾符,一旦一個共享變量(類的成員變量、類的靜態成員變量)被Volatile修飾之後,那麼就具備瞭兩層語義:

1、保證多線程下的可見性

2、禁止進行指令重排序(即保證有序性)

這裡需要註意一個問題,Volatile 隻能讓被他修飾內容具有可見性、有序性 。Volatile隻能保證對單次讀/寫的原子性,i++ 這種操作不能保證原子性。

Volatile 的內存模型

Volatile 的內存模型

**Java 內存模型(JMM)**是一種抽象的概念,並不真實存在,它描述瞭一組規則或規范,通過這組規范定義瞭程序中各個變量(包括實例字段、靜態字段和構成數組對象的元素)的訪問方式。

試圖屏蔽各種硬件和操作系統的內存訪問差異,以實現讓 Java 程序在各種平臺下都能達到一致的內存訪問效果。

Java內存模型規定瞭所有的變量都存儲在主內存中,每條線程還有自己的工作內存,線程的工作內存中保存瞭該線程中是用到的變量的主內存副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存。不同的線程之間也無法直接訪問對方工作內存中的變量,線程間變量的傳遞均需要自己的工作內存和主存之間進行數據同步進行。

  • 主內存 主要存儲的是Java實例對象,所有線程創建的實例對象都存放在主內存中,不管該實例對象是成員變量還是方法中的本地變量(也稱局部變量),當然也包括瞭共享的類信息、常量、靜態變量。由於是共享數據區域,多條線程對同一個變量進行訪問可能會發現線程安全問題。
  • 工作內存 每條線程都有自己的工作內存(Working Memory,又稱本地內存,可與前面介紹的處理器高速緩存類比),線程的工作內存中保存瞭該線程使用到的變量的主內存中的共享變量的副本拷貝。

工作內存是 JMM 的一個抽象概念,並不真實存在 。它涵蓋瞭緩存,寫緩沖區,寄存器以及其他的硬件和編譯器優化。

主要存儲當前方法的所有本地變量信息(工作內存中存儲著主內存中的變量副本拷貝),每個線程隻能訪問自己的工作內存,即線程中的本地變量對其它線程是不可見的,就算是兩個線程執行的是同一段代碼,它們也會各自在自己的工作內存中創建屬於當前線程的本地變量,當然也包括瞭字節碼行號指示器、相關Native方法的信息。

Volatile 的實現原理

Volatile 保證內存可見性

Volatile 的實現原理

主內存和工作內存之間的交互有具體的交互協議,JMM定義瞭 八種操作 來完成,這八種操作是 原子的 、 不可再分的 ,它們分別是:lockunlockreadloaduseassignstorewrite

其中,lock , unlock , read , write 作用於主內存; load ,use , assign , store 作用於工作內存。

(1) lock

將主內存中的變量鎖定,為一個線程所獨占。

(2) unclock

將lock加的鎖定解除,此時其它的線程可以有機會訪問此變量。

(3) read

將主內存中的變量值讀到工作內存當中。

(4) load

將read讀取的值保存到工作內存中的變量副本中。

(5) use

將值傳遞給線程的代碼執行引擎。

(6) assign

將執行引擎處理返回的值重新賦值給變量副本。

(7) store

將變量副本的值存儲到主內存中。

(8) write

將 store 存儲的值寫入到主內存的共享變量當中。

  • 從主存復制變量到當前工作內存(read and load)
  • 執行代碼,改變共享變量值 (use and assign)
  • 用工作內存數據刷新主存相關內容 (store and write)

指令規則

  • read 和 load、store 和 write 必須成對出現。
  • assign 操作,工作內存變量改變後必須刷回主內存。
  • 同一時間隻能運行一個線程對變量進行 lock,當前線程 lock 可重入,unlock 次數必須等於 lock 的次數,該變量才能解鎖。
  • 對一個變量 lock 後,會清空該線程工作內存變量的值,重新執行 load 或者 assign 操作初始化工作內存中變量的值。
  • unlock 前,必須將變量同步到主內存( store/write 操作)。

Volatile源碼案例

Volatile源碼案例

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!

推薦閱讀: