天天看點

你應該知道的 volatile 關鍵字

不管是在面試還是實際開發中都是一個應該掌握的技能。

不管是在面試還是實際開發中 <code>volatile</code> 都是一個應該掌握的技能。

首先來看看為什麼會出現這個關鍵字。

由于 <code>Java</code> 記憶體模型(<code>JMM</code>)規定,所有的變量都存放在主記憶體中,而每個線程都有着自己的工作記憶體(高速緩存)。

線程在工作時,需要将主記憶體中的資料拷貝到工作記憶體中。這樣對資料的任何操作都是基于工作記憶體(效率提高),并且不能直接操作主記憶體以及其他線程工作記憶體中的資料,之後再将更新之後的資料重新整理到主記憶體中。

這裡所提到的主記憶體可以簡單認為是堆記憶體,而工作記憶體則可以認為是棧記憶體。

如下圖所示:

是以在并發運作時可能會出現線程 B 所讀取到的資料是線程 A 更新之前的資料。

顯然這肯定是會出問題的,是以 <code>volatile</code> 的作用出現了:

當一個變量被 <code>volatile</code> 修飾時,任何線程對它的寫操作都會立即重新整理到主記憶體中,并且會強制讓緩存了該變量的線程中的資料清空,必須從主記憶體重新讀取最新資料。

<code>volatile</code> 修飾之後并不是讓線程直接從主記憶體中擷取資料,依然需要将變量拷貝到工作記憶體中。

當我們需要在兩個線程間依據主記憶體通信時,通信的那個變量就必須的用 <code>volatile</code> 來修飾:

主線程在修改了标志位使得線程 A 立即停止,如果沒有用 <code>volatile</code> 修飾,就有可能出現延遲。

但這裡有個誤區,這樣的使用方式容易給人的感覺是:

對 <code>volatile</code> 修飾的變量進行并發操作是線程安全的。

這裡要重點強調,<code>volatile</code> 并不能保證線程安全性!

如下程式:

當我們三個線程(t1,t2,main)同時對一個 <code>int</code> 進行累加時會發現最終的值都會小于 30000。

這是因為雖然 <code>volatile</code> 保證了記憶體可見性,每個線程拿到的值都是最新值,但 <code>count ++</code> 這個操作并不是原子的,這裡面涉及到擷取值、自增、指派的操作并不能同時完成。

是以想到達到線程安全可以使這三個線程串行執行(其實就是單線程,沒有發揮多線程的優勢)。

也可以使用 <code>synchronize</code> 或者是鎖的方式來保證原子性。

還可以用 <code>Atomic</code> 包中 <code>AtomicInteger</code> 來替換 <code>int</code>,它利用了 <code>CAS</code> 算法來保證了原子性。

記憶體可見性隻是 <code>volatile</code> 的其中一個語義,它還可以防止 <code>JVM</code> 進行指令重排優化。

舉一個僞代碼:

一段特别簡單的代碼,理想情況下它的執行順序是:<code>1&gt;2&gt;3</code>。但有可能經過 JVM 優化之後的執行順序變為了 <code>2&gt;1&gt;3</code>。

可以發現不管 JVM 怎麼優化,前提都是保證單線程中最終結果不變的情況下進行的。

可能這裡還看不出有什麼問題,那看下一段僞代碼:

這裡就能看出問題了,當 <code>flag</code> 沒有被 <code>volatile</code> 修飾時,<code>JVM</code> 對 1 和 2 進行重排,導緻 <code>value</code> 都還沒有被初始化就有可能被線程 B 使用了。

是以加上 <code>volatile</code> 之後可以防止這樣的重排優化,保證業務的正确性。

一個經典的使用場景就是雙重懶加載的單例模式了:

這裡的 <code>volatile</code> 關鍵字主要是為了防止指令重排。

如果不用 ,<code>singleton = new Singleton();</code>,這段代碼其實是分為三步:

配置設定記憶體空間。(1)

初始化對象。(2)

将 <code>singleton</code> 對象指向配置設定的記憶體位址。(3)

加上 <code>volatile</code> 是為了讓以上的三步操作順序執行,反之有可能第二步在第三步之前被執行就有可能某個線程拿到的單例對象是還沒有初始化的,以緻于報錯。

<code>volatile</code> 在 <code>Java</code> 并發中用的很多,比如像 <code>Atomic</code> 包中的 <code>value</code>、以及 <code>AbstractQueuedLongSynchronizer</code> 中的 <code>state</code> 都是被定義為 <code>volatile</code> 來用于保證記憶體可見性。

将這塊了解透徹對我們編寫并發程式時可以提供很大幫助。

最近在總結一些 Java 相關的知識點,感興趣的朋友可以一起維護。

位址: https://github.com/crossoverJie/Java-Interview

作者:

crossoverJie

出處:

https://crossoverjie.top

你應該知道的 volatile 關鍵字

歡迎關注部落客公衆号與我交流。

本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出,

如有問題, 可郵件(crossoverJie#gmail.com)咨詢。

上一篇: XSD 空元素
下一篇: XSD 僅含元素