中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何理解volatile關鍵字的使用場景及其原理

發布時間:2021-10-18 17:30:14 來源:億速云 閱讀:132 作者:iii 欄目:編程語言

本篇內容主要講解“如何理解volatile關鍵字的使用場景及其原理”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“如何理解volatile關鍵字的使用場景及其原理”吧!

一、 Java 線程的內存工作模型

在當前的Java內存模型下(JVM 1.2之后),線程可以把變量保存在本地內存(比如機器的寄存器)中,而不是直接在主存中進行讀寫。如圖:

 如何理解volatile關鍵字的使用場景及其原理

1.1 我們來看一下例子

如何理解volatile關鍵字的使用場景及其原理

當 signal 為false時 , run 方法會終止。  上訴代碼能否實現我們想要的效果。

我們來看執行結果:

如何理解volatile關鍵字的使用場景及其原理

分析:

如何理解volatile關鍵字的使用場景及其原理

從橫向去看看,線程A和線程B就好像通過共享變量在進行隱式通信。 如果線程A更新后數據并沒有及時通知線程B,而此時線程B讀到的是過期的數據。也就是發生了緩解數據不一致的情況。  

如何解決?

可以通過同步機制(控制不同線程間操作發生的相對順序)來解決或者通過volatile關鍵字使得每次volatile變量都能夠強制刷新到主存,從而對每個線程都是可見的。volatile相較與同步機制會更輕量,性能更好。

修改代碼:

如何理解volatile關鍵字的使用場景及其原理

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

如何理解volatile關鍵字的使用場景及其原理

二、volatile底層原理

volatile從內存語義上來看:

當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存

當讀一個volatile變量時,線程接下來將從主內存中讀取共享變量。

那底層的實現原理是什么?

2.1 首先,查看字節碼(javac \ javap)

如何理解volatile關鍵字的使用場景及其原理

然后再編譯成匯編語言(hsdis)

Java -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly SingleInstance

親們實在看不懂,只能通過比較下有關鍵字volatile與沒有的差異。

可以發現多出來好多 lock addl

如何理解volatile關鍵字的使用場景及其原理

這是個啥?

2.2內存屏障

內存屏障(Memory Barrier)與 內存柵欄(intel稱之為 Memory Fence)是同一個概念,不同的叫法。可以通過插入內存屏障指令來禁止特定類型的處理器重排序。

volatile的底層實現是通過插入內存屏障,JMM采用保守策略。如下:

在每一個volatile寫操作前面插入一個StoreStore屏障

在每一個volatile寫操作后面插入一個StoreLoad屏障

在每一個volatile讀操作后面插入一個LoadLoad屏障

在每一個volatile讀操作后面插入一個LoadStore屏障

StoreStore屏障可以保證在volatile寫之前,其前面的所有普通寫操作都已經刷新到主內存中;

StoreLoad屏障的作用是避免volatile寫與后面可能有的volatile讀/寫操作重排序;

LoadLoad屏障用來禁止處理器把上面的volatile讀與下面的普通讀重排序;

LoadStore屏障用來禁止處理器把上面的volatile讀與下面的普通寫重排序;

2.3指令重排序

在執行程序時為了提高性能,編譯器和處理器常常會對指令做重排序。

指令重排序的目的是為了提高性能,指令重排序僅保證在單線程下不會改變最終的執行結果,但無法保證在多線程下的執行結果。

從java源代碼到最終實際執行的指令序列,會分別經歷下面三種重排序:

如何理解volatile關鍵字的使用場景及其原理

上述的1屬于編譯器重排序,2和3屬于處理器重排序。這些重排序都可能會導致多線程程序出現內存可見性問題。

對于編譯器,JMM的編譯器重排序規則會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。

對于處理器重排序,JMM的處理器重排序規則會要求java編譯器在生成指令序列時,插入特定類型的內存屏障指令,通過內存屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)。

JMM屬于語言級的內存模型,它確保在不同的編譯器和不同的處理器平臺之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內存可見性保證。 

2.4 程序員密切相關的happens-before規則

從JDK5開始,java使用新的JSR -133內存模型(本文除非特別說明,針對的都是JSR- 133內存模型)。JSR-133使用happens-before的概念來闡述操作之間的內存可見性。在JMM中,如果一個操作執行的結果需要對另一個操作可見,那么這兩個操作之間必須要存在happens-before關系。這里提到的兩個操作既可以是在一個線程之內,也可以是在不同線程之間。

程序順序規則:

一個線程中的每個操作,happens- before 于該線程中的任意后續操作。

監視器鎖規則:

對一個監視器鎖的解鎖,happens- before 于隨后對這個監視器鎖的加鎖。

volatile變量規則:對一個volatile域的寫,happens- before 于任意后續對這個volatile域的讀。

傳遞性:

如果A happens- before B,且B happens- before C,那么A happens- before C。

如何理解volatile關鍵字的使用場景及其原理

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

2.5來看一個例子 -- 雙重檢測的單例

如何理解volatile關鍵字的使用場景及其原理

請問這段單例代碼有問題嗎?

分析:  

instance = new TestInstance();可以分解為3行偽代碼 

如何理解volatile關鍵字的使用場景及其原理

假設有線程A 執行到 step 3, 且編譯器進行指令重排為Step a-c-b,正好行程A剛執行完Step c,然后線程B執行到 step 1 , 我們來看看會發生什么?

線程B 判斷 instance==null 為false ,直接返回 instance; 而此時instance只執行了 Step c. instance = memory //設置instance指向剛分配的地址,內存地址中的對象尚未初始化完成。

要解決這個問題可將代碼修改為:

private volatile static SingleInstance instance = null;

三、volatile能保證原子性嗎?

看看以下描述:“volatile變量對所有線程是立即可見的,對volatile變量所有的寫操作都能立刻反應到其他線程之中,換句話說,volatile變量在各個線程中是一致的,所以基于volatile變量的運算在并發下是安全的”。

這句話的論據部分并沒有錯,但是其論據并不能得出“基于volatile變量的運算在并發下是安全的”這個結論。

volatile變量在各個線程的工作內存中不存在一致性問題,但是Java里面的運算并非原子操作,并且volatile并不能保證原子性,導致volatile變量的運算在并發下一樣是不安全的,我們可以通過一段簡單的演示來說明原因,請看下面的例子  

如何理解volatile關鍵字的使用場景及其原理

輸出結果:

如何理解volatile關鍵字的使用場景及其原理

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

如何理解volatile關鍵字的使用場景及其原理

再看看這段代碼的字節碼:

如何理解volatile關鍵字的使用場景及其原理

我們將 id++ 簡單概括為三個操作:

1.讀取變量id的值;  -- volatale 保證此處跟主存一致

2.將變量id的值加1; 

3.將計算后的值再賦值給變量id的引用。

其中 2、3 不能線程安全.

想要保證原子性,可以使用請同步機制, 以下是采用一種原子操作的數據結構 AtomicInteger.

到此,相信大家對“如何理解volatile關鍵字的使用場景及其原理”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

静乐县| 弋阳县| 婺源县| 中方县| 静宁县| 宜川县| 云梦县| 浙江省| 长白| 乐昌市| 常州市| 江川县| 张家界市| 奈曼旗| 南昌县| 儋州市| 红桥区| 鄂托克旗| 苍山县| 南溪县| 马龙县| 都兰县| 张北县| 金湖县| 呼玛县| 承德市| 张家川| 治县。| 文登市| 青浦区| 抚宁县| 县级市| 黔西县| 景德镇市| 原平市| 苍溪县| 孟津县| 台南县| 蒙山县| 淳安县| 慈利县|