本篇內(nèi)容主要講解“如何理解volatile關鍵字的使用場景及其原理”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“如何理解volatile關鍵字的使用場景及其原理”吧!
創(chuàng)新互聯(lián)自2013年創(chuàng)立以來,先為丘北等服務建站,丘北等地企業(yè),進行企業(yè)商務咨詢服務。為丘北企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務解決您的所有建站問題。
一、 Java 線程的內(nèi)存工作模型
在當前的Java內(nèi)存模型下(JVM 1.2之后),線程可以把變量保存在本地內(nèi)存(比如機器的寄存器)中,而不是直接在主存中進行讀寫。如圖:

1.1 我們來看一下例子

當 signal 為false時 , run 方法會終止。 上訴代碼能否實現(xiàn)我們想要的效果。
我們來看執(zhí)行結果:

分析:

從橫向去看看,線程A和線程B就好像通過共享變量在進行隱式通信。 如果線程A更新后數(shù)據(jù)并沒有及時通知線程B,而此時線程B讀到的是過期的數(shù)據(jù)。也就是發(fā)生了緩解數(shù)據(jù)不一致的情況。
如何解決?
可以通過同步機制(控制不同線程間操作發(fā)生的相對順序)來解決或者通過volatile關鍵字使得每次volatile變量都能夠強制刷新到主存,從而對每個線程都是可見的。volatile相較與同步機制會更輕量,性能更好。
修改代碼:

可以得出我們想要的結果:

二、volatile底層原理
volatile從內(nèi)存語義上來看:
當寫一個volatile變量時,JMM會把該線程對應的本地內(nèi)存中的共享變量刷新到主內(nèi)存
當讀一個volatile變量時,線程接下來將從主內(nèi)存中讀取共享變量。
那底層的實現(xiàn)原理是什么?
2.1 首先,查看字節(jié)碼(javac \ javap)

然后再編譯成匯編語言(hsdis)
Java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly SingleInstance
親們實在看不懂,只能通過比較下有關鍵字volatile與沒有的差異。
可以發(fā)現(xiàn)多出來好多 lock addl

這是個啥?
2.2內(nèi)存屏障
內(nèi)存屏障(Memory Barrier)與 內(nèi)存柵欄(intel稱之為 Memory Fence)是同一個概念,不同的叫法。可以通過插入內(nèi)存屏障指令來禁止特定類型的處理器重排序。
volatile的底層實現(xiàn)是通過插入內(nèi)存屏障,JMM采用保守策略。如下:
在每一個volatile寫操作前面插入一個StoreStore屏障
在每一個volatile寫操作后面插入一個StoreLoad屏障
在每一個volatile讀操作后面插入一個LoadLoad屏障
在每一個volatile讀操作后面插入一個LoadStore屏障
StoreStore屏障可以保證在volatile寫之前,其前面的所有普通寫操作都已經(jīng)刷新到主內(nèi)存中;
StoreLoad屏障的作用是避免volatile寫與后面可能有的volatile讀/寫操作重排序;
LoadLoad屏障用來禁止處理器把上面的volatile讀與下面的普通讀重排序;
LoadStore屏障用來禁止處理器把上面的volatile讀與下面的普通寫重排序;
2.3指令重排序
在執(zhí)行程序時為了提高性能,編譯器和處理器常常會對指令做重排序。
指令重排序的目的是為了提高性能,指令重排序僅保證在單線程下不會改變最終的執(zhí)行結果,但無法保證在多線程下的執(zhí)行結果。
從java源代碼到最終實際執(zhí)行的指令序列,會分別經(jīng)歷下面三種重排序:

上述的1屬于編譯器重排序,2和3屬于處理器重排序。這些重排序都可能會導致多線程程序出現(xiàn)內(nèi)存可見性問題。
對于編譯器,JMM的編譯器重排序規(guī)則會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。
對于處理器重排序,JMM的處理器重排序規(guī)則會要求java編譯器在生成指令序列時,插入特定類型的內(nèi)存屏障指令,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)。
JMM屬于語言級的內(nèi)存模型,它確保在不同的編譯器和不同的處理器平臺之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內(nèi)存可見性保證。
2.4 程序員密切相關的happens-before規(guī)則
從JDK5開始,java使用新的JSR -133內(nèi)存模型(本文除非特別說明,針對的都是JSR- 133內(nèi)存模型)。JSR-133使用happens-before的概念來闡述操作之間的內(nèi)存可見性。在JMM中,如果一個操作執(zhí)行的結果需要對另一個操作可見,那么這兩個操作之間必須要存在happens-before關系。這里提到的兩個操作既可以是在一個線程之內(nèi),也可以是在不同線程之間。
程序順序規(guī)則:
一個線程中的每個操作,happens- before 于該線程中的任意后續(xù)操作。
監(jiān)視器鎖規(guī)則:
對一個監(jiān)視器鎖的解鎖,happens- before 于隨后對這個監(jiān)視器鎖的加鎖。
volatile變量規(guī)則:對一個volatile域的寫,happens- before 于任意后續(xù)對這個volatile域的讀。
傳遞性:
如果A happens- before B,且B happens- before C,那么A happens- before C。

如上圖所示,一個happens-before規(guī)則通常對應于多個編譯器和處理器重排序規(guī)則。對于java程序員來說,happens-before規(guī)則簡單易懂,它避免java程序員為了理解JMM提供的內(nèi)存可見性保證而去學習復雜的重排序規(guī)則以及這些規(guī)則的具體實現(xiàn)。
2.5來看一個例子 -- 雙重檢測的單例

請問這段單例代碼有問題嗎?
分析:
instance = new TestInstance();可以分解為3行偽代碼

假設有線程A 執(zhí)行到 step 3, 且編譯器進行指令重排為Step a-c-b,正好行程A剛執(zhí)行完Step c,然后線程B執(zhí)行到 step 1 , 我們來看看會發(fā)生什么?
線程B 判斷 instance==null 為false ,直接返回 instance; 而此時instance只執(zhí)行了 Step c. instance = memory //設置instance指向剛分配的地址,內(nèi)存地址中的對象尚未初始化完成。
要解決這個問題可將代碼修改為:
private volatile static SingleInstance instance = null;
三、volatile能保證原子性嗎?
看看以下描述:“volatile變量對所有線程是立即可見的,對volatile變量所有的寫操作都能立刻反應到其他線程之中,換句話說,volatile變量在各個線程中是一致的,所以基于volatile變量的運算在并發(fā)下是安全的”。
這句話的論據(jù)部分并沒有錯,但是其論據(jù)并不能得出“基于volatile變量的運算在并發(fā)下是安全的”這個結論。
volatile變量在各個線程的工作內(nèi)存中不存在一致性問題,但是Java里面的運算并非原子操作,并且volatile并不能保證原子性,導致volatile變量的運算在并發(fā)下一樣是不安全的,我們可以通過一段簡單的演示來說明原因,請看下面的例子

輸出結果:

為什么會這樣,我們再來分析下:

再看看這段代碼的字節(jié)碼:

我們將 id++ 簡單概括為三個操作:
1.讀取變量id的值; -- volatale 保證此處跟主存一致
2.將變量id的值加1;
3.將計算后的值再賦值給變量id的引用。
其中 2、3 不能線程安全.
想要保證原子性,可以使用請同步機制, 以下是采用一種原子操作的數(shù)據(jù)結構 AtomicInteger.
到此,相信大家對“如何理解volatile關鍵字的使用場景及其原理”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!
標題名稱:如何理解volatile關鍵字的使用場景及其原理
文章來源:http://www.chinadenli.net/article8/piseip.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站維護、App開發(fā)、自適應網(wǎng)站、品牌網(wǎng)站制作、網(wǎng)站建設、電子商務
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)