一文讀懂Java關鍵詞之volatile作用(內存屏障)

2020-12-17 CoderStudio

之前在一篇文章中跟大家一同學習了CPU緩存一致性,通過緩存一致性協議MESI我們可以讓CPU各個計算核心中緩存的數據保持一致,避免造成計算結果的差異。

我們還知道Java內存模型中,各個線程還保存了一份主內存中數據的拷貝,那麼不同線程的拷貝應該如何保證數據的一致性呢,今天我們就跟大家一起看看其中的一個技術。

一、問題

有如下代碼:

代碼意思很簡單,如果看不懂的可以私信我或者+我討論。

我們重點關注work這個方法,worker在工作前需要先看看自己是否在onWork狀態,如果一個worker工作在一個線程中,那麼onWork的狀態改變我們可以確信是不會有問題的,肯定是按照指令順序執行的。

多線程狀態下會不會有問題?當然會!看如下狀態切換:

1,onWork狀態初始為false;

2,線程A將onWork狀態切換為true,但是true值還保留在線程A的局部內存中,沒有刷新回主內存;

3,線程B調用work方法,但是這個時候看到的onWork狀態還是false,所以認為自己還是下班狀態,所以不上工。

上面第3步裡面就出錯了,本來已經上班的worker,卻出現了下班狀態的輸出!

二、內存屏障

那上面的問題應該怎麼解決呢?我們分析一下問題出在哪裡。

其實很簡單,就是線程A在給onWork賦值之後沒有將最新的值從本地內存中刷回到主內存!

如果線程A更新了值,並且刷回了主內存,線程B在使用onWork之前是從主內存中讀取最新的值而不使用局部內存中的值,那麼這樣就肯定能夠避免上述問題了。

Java中也確實是這麼做的,這個技術就是內存屏障,或者叫內存柵欄,以Coder大白話理解就是當CPU遇到某個特殊變量的時候,會碰到一個柵欄,這個柵欄會攔住你繼續往下執行而讓你必須跟主內存進行一次交互,所以叫內存柵欄。這個道理是不是很簡單?但是確實解決了問題。

三、Volatile

在上面coder的大白話理解中提到CPU遇到特殊變量才有產生內存柵欄的效果,那麼這個特殊變量如何定義呢?

很簡單,就是在變量前面加上volatile關鍵字,所以上面的代碼,我們修改如下:

這樣當某個核心對onWork進行賦值之後,CPU會強制將這個值寫回主內存,在遇到要讀取onWork狀態的時候,必須從主內存中讀取最新的值。這樣就可以保證每個線程在使用的時候能夠讀取最新的狀態,這便是內存的可見性

四、volatile的不可用場景

還記得之前我們試圖用volatile來解決同步問題嗎?代碼在這裡:

但是當在多個線程中執行同一個seller對象的時候,發現ticket並不是按照我們想像的一個個遞增的,那麼問題出在哪裡?

上面講到volatile只解決內存的可見性,並不能解決內存的原子性,看如下流程:

1,ticket初始值為0;

2,線程A調用increment將ticket的值更新為1,並且刷回主內存;

3,線程B和線程C同時調用increment方法,這個時候先從主內存讀取ticket的值,兩個線程都讀到了1,並且自增之後刷回主內存,這個時候主內存ticket的值是多少?沒錯,是2!

上面流程裡面3個線程調用了3次ticket自增,但是最後ticket的值不是3而是2。

這裡想要得到正確的值,還需要其他技術,比如同步、鎖機制,我們後面跟大家分享。

這裡簡單總結一下volatile不適用場景:volatile修飾的變量的值的更改不能依賴於前值。比如onWork的狀態更改不依賴之前到底是true還是false,但是ticket的更改因為是自增所以要依賴前值。

五、結語

今天跟大家分享了內存屏障的概念和volatile的用法,volatile可以作為輕量級的同步機制使用,但是大家一定注意其不適用的場景。關於兩個線程重量級的同步機制,這個也是面試之中常見的考點,希望大家能掌握volatile關鍵字的作用,可以私我,解答疑問,相互提升。

三人行必有我師焉!

相關焦點

  • volatile如何防止指令重排序?原來使用了內存屏障
    整個java並發體系也是圍繞著如何解決這三個問題來設計的。volatile關鍵字也不例外,我們都知道它解決了可見性和有序性,但是不能保證原子性。這篇文章也主要基於其中一個特性,也就是研究一下volatile是如何保證有序性的。
  • Java研發技術——Volatile原理詳解
    內存屏障是一個 CPU 指令,不同的硬體實現內存屏障的手段不一樣,java通過屏蔽這些差異,統一由jvm來生成內存屏障的指令。java中內存屏障的兩個作用#阻止屏障兩側的代碼指令重排序強制將工作內存中的數據寫回主內存,讓工作內存中的數據失效。
  • 1分鐘讀懂java中的volatile關鍵字
    本文將以儘量簡潔的方式介紹java中的volatile關鍵字。如果覺得寫的不錯,記得,如果寫的不好歡迎批評指正,讓我們一起進步!volatile作為java中的關鍵詞之一,用以聲明變量的值可能隨時會別的線程修改,使用volatile修飾的變量會強制將修改的值立即寫入主存,主存中值的更新會使緩存中的值失效(非volatile變量不具備這樣的特性,非volatile變量的值會被緩存,線程A更新了這個值,線程B讀取這個變量的值時可能讀到的並不是是線程A更新後的值)。
  • Java內存模型與volatile關鍵字
    Java的內存模型大概樣子還是有必要了解下的,今天就學習了下,順便學習了一點volatile關鍵字!每個線程獲取這類變量都是先把變量從主內存加載到工作內存,然後才能進行使用,同樣如果是修改,那麼也是先修改了工作內存中的變量,然後再從工作內存同步進主內存中。
  • Java程式設計師面試必備:Volatile全方位解析
    內存屏障內存屏障四大分類:(Load 代表讀取指令,Store代表寫入指令)為了實現volatile的內存語義,Java內存模型採取以下的保守策略在每個volatile寫操作的前面插入一個StoreStore屏障。
  • java並發編程之volatile關鍵字
    一、volatile使用方式1、用作全局標誌我們在開發中,對於一個死循環線程的控制,基本上是要用到一個全局的開關標誌的但是由於作業系統可以對指令進行重排序,所以上面的過程也可能會變成如下過程:(1)分配內存空間。(2)將內存空間的地址賦值給對應的引用。
  • Java中的volatile變量適合於什麼場景下應用
    為什麼這個值不對,大家都知道java是值傳遞,JVM在運行時內存分配匯總有一個內存區域稱為虛擬機棧, 線程棧保存了線程運行時的信息,當線程訪問某個對象的值的時候,首先通過對象的引用找到對應在堆內存的變量的值,然後把堆內存變量的值load到本地內存中(當前線程所分配內存區域),建立一個變量副本, 之後線程不再和對象在堆內存的變量有任何聯繫,而是直接修改副本的值,在修改完成之後自動把變量副本寫回堆內存,
  • Java 多線程 —— 深入理解 volatile 的原理以及應用
    推薦閱讀:《java 多線程—線程怎麼來的》這一篇主要講解一下volatile的原理以及應用,想必看完這一篇之後,你會對volatile的應用原理以及使用邊界會有更深刻的認知。happens-before是java內存模型向我們提供的內存可見性保證,這也就是我們第一個問題的解答,volatiel如何保證對共享變量同步的。
  • Java並發之volatile關鍵字內存可見性問題
    Java並發之volatile關鍵字內存可見性問題線程之間數據共享案例我們先來看一個場景:Main函數啟動後,調用一個線程向list中添加數據。而主內存(也就是系統內存非程序自己需要的內存)flag變量對所有共享這個變量的線程來說,都應該是可見的才可以。那麼這個時候,在Java中怎麼實現線程之間共享數據的內存可見性呢?這裡就是我們今天需要講解的關鍵字:volatile。
  • volatile關鍵字詳解
    處理器使用嗅探技術保證它的內部緩存,系統內存和其他處理器的緩存在總線上保持一致寫一個volatile變量時,JMM(java共享內存模型)會把該線程對應的本地內存中的共享變量值刷新到主內存;當讀一個volatile變量時,JMM會把該線程對應的本地內存置為無效,線程接下來從主內存中讀取共享變量。
  • 從零開始了解多線程知識之開始篇目——jvm&volatile
    synchronized和Lock也可以保證可見性,因為它們可以保證任一時刻只有一個線程能訪問共享資源,並在其釋放鎖之前將修改的變量刷新到內存中有序性問題在Java裡面,可以通過volatile關鍵字來保證一定的「有序性」(具體原理在下一節講述volatile關鍵字)。
  • 你應該要理解的java並發關鍵字volatile
    提高java的並發編程,就不得不提volatile關鍵字,不管是在面試還是實際開發中 volatile都是一個應該掌握的技能。他的重要性不言而喻。因此也有必要學好。一、為什麼要用到volatile關鍵字?
  • java基礎之volatile關鍵字
    可見性的含義:可見性的意思是指當一個線程修改一個共享變量時,另外一個線程能讀到這個修改後的值,volatile作用就是當一個線程修改了共享變量的值,其他線程馬上就能知道。Java內存模型規定所有的變量都是存在主存當中,每個線程都有自己的工作內存。
  • 知名公司面試題:談談你對volatile關鍵字的理解
    作為一名java程式設計師,求職面試時,關於volatile關鍵字時常會遇到。張工最近到某知名網際網路公司面試,面試官提出這樣的一個問題:談談你對volatile關鍵字的理解張工一時間沒有回答上來,面試官:你都工作三年了,怎麼對volatile關鍵字都沒掌握啊。
  • 面試必問的volatile,你了解多少
    2、CPU執行store寫數據時,把數據寫到StoreBuffer中,待到某個適合的時間點,把StoreBuffer的數據刷到主存中。2、其中 _a(%rip)是變量a的每次地址,通過 movl $2,_a(%rip)可以把變量a所在的內存設置成2,關於RIP,可以查看 x64下PIC的新尋址方式:RIP相對尋址所以,每次對變量a的賦值,都會寫入到內存中
  • Java中 volatile 關鍵字的最全總結,趕快給自己查缺補漏吧!
    也即當一條線程修改了共享變量的值,新值對於其他線程來說是可以立即得知的。如果沒有同步機制提供的這種可見性保證,線程看到的共享變量可能是修改前的值或不一致的值,這將引發許多嚴重問題。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:a.對變量的寫操作不依賴於當前值。
  • Java中volatile特性概述
    volatile特性概述volatile總體概覽在上節中,我們已經研究完了volatile可以實現並發下共享變量的可見性,除了volatile可以保證可見性外,volatile 還具備如下一些突出的特性:volatile的原子性問題:volatile
  • 深入synchronized和volatile底層原理
    什麼緩存一致性協議問題,內存屏障問題,都是硬體層面的問題(還有作業系統層面的),還有JAVA號稱一處開發多處運行的存在,是不可能允許這樣的問題存在的。於是JAVA虛擬機規範就試圖定義一種自己的內存模型來屏蔽上面的問題。
  • 就是要你懂 Java 中 volatile 關鍵字實現原理
    前言我們知道volatile關鍵字的作用是保證變量在多線程之間的可見性,它是java.util.concurrent包的核心,沒有volatile就沒有這麼多的並發類給我們使用基於此,現在CPU大多數情況下讀寫都不會直接訪問內存(CPU都沒有連接到內存的管腳),取而代之的是CPU緩存,CPU緩存是位於CPU與內存之間的臨時存儲器,它的容量比內存小得多但是交換速度卻比內存快得多。而緩存中的數據是內存中的一小部分數據,但這一小部分是短時間內CPU即將訪問的,當CPU調用大量數據時,就可先從緩存中讀取,從而加快讀取速度。
  • Java中volatile關鍵字概覽
    一、第一章 volatile關鍵字概覽多線程下變量的不可見性概述在多線程並發執行下,多個線程修改共享的成員變量,會出現一個線程修改了共享變量的值後,另一個線程不能直接 看到該線程修改後的變量的最新值。JMM(Java Memory Model):Java內存模型,是java虛擬機規範中所定義的一種內存模型,Java內存模型是標準化的,屏蔽掉了底層不同計算機的區別。