精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

99%的人沒弄懂Volatile的設計原理,更別說靈活運用了

開發 架構
最初的CPU是沒有緩存區的,CPU直接讀寫內存。但這就存在一個問題,CPU的運行效率與讀寫內存的效率差距百倍以上??偛荒蹸PU執行1個寫操作耗時1個時鐘周期,然后再等待內存執行一百多個時鐘周期吧。

[[427372]]

本文轉載自微信公眾號「程序新視界」,作者二師兄。轉載本文請聯系程序新視界公眾號。

寫volatile的文章非常多,本人也看過許多相關文章,但始終感覺有哪里不太明白,但又說不上來說為什么??赡苁沁^于追求底層實現原理,老想問一個為什么吧。

而寫這篇文章的目的很簡單,就是突然之間明白了volatile為什么要這樣設計了。好東西當然要拿出來分享了,于是就有了這篇文章。

我們就從硬件到軟件,再到具體的案例來聊聊volatile的底層原理,文章比較長,可收藏之后閱讀。

CPU緩存的出現

最初的CPU是沒有緩存區的,CPU直接讀寫內存。但這就存在一個問題,CPU的運行效率與讀寫內存的效率差距百倍以上??偛荒蹸PU執行1個寫操作耗時1個時鐘周期,然后再等待內存執行一百多個時鐘周期吧。

于是在CPU和內存之間添加了緩存(CPU緩存:Cache Memory),它是位于CPU和內存之間的臨時存儲器。這就像當Mysql出現瓶頸時,我們會考慮通過緩存數據來提升性能一樣??傊珻PU緩存的出現就是為了解決CPU和內存之間處理速度不匹配的問題而誕生的。

這時,我們有一個粗略的圖:

CPU-CPU緩存-內存

但考慮到進一步優化數據的調度,CPU緩存又分為一級緩存、二級緩存、三級緩存等。它們主要用于優化數據的吞吐和暫存,提高執行效率。

目前主流CPU通常采用三層緩存:

  • 一級緩存(L1 Cache):主要用于緩存指令(L1P)和緩存數據(L1D),指令和數據是分開存儲的。一級緩存屬于核心獨享,比如4核電腦,則有4個L1。
  • 二級緩存(L2 Cache):二級緩存的指令和數據是共享的,二級緩存的容量會直接影響CPU的性能,越大越好。二級緩存同樣屬于核心獨享,4核心電腦,則有4個L2。
  • 三級緩存(L3 Cache):作用是進一步降低內存的延遲,同時提升海量數據計算的性能。三級緩存屬于核心共享的,因此只有1個。

經過上述細分,可以將上圖進一步細化:

CPU三級緩存

這里再補充一個概念:緩存行(Cache-line),它是CPU緩存存儲數據的最小單位,后面會用到。上面的CPU緩存,也稱作高速緩存。

引入緩存之后,每個CPU的處理過程為:先將計算所需數據緩存在高速緩存中,當CPU進行計算時,直接從高速緩存讀取數據,計算完成再寫入緩存中。當整個運算過程完成之后,再把緩存中的數據同步到主內存中。

如果是單核CPU這樣處理沒有什么問題。但在多核系統中,每個CPU都可能將同一份數據緩存到自己的高速緩存中,這就出現了緩存數據一致性問題了。

CPU層提供了兩種解決方案:總線鎖和緩存一致性。

總線鎖

前端總線(也叫CPU總線)是所有CPU與芯片組連接的主干道,負責CPU與外界所有部件的通信,包括高速緩存、內存、北橋,其控制總線向各個部件發送控制信號、通過地址總線發送地址信號指定其要訪問的部件、通過數據總線雙向傳輸。

比如CPU1要操作共享內存數據時,先在總線上發出一個LOCK#信號,其他處理器就不能操作緩存了該共享變量內存地址的緩存,也就是阻塞了其他CPU,使該處理器可以獨享此共享內存。

很顯然,這樣的做法代價十分昂貴,于是為了降低鎖粒度,CPU引入了緩存鎖。

緩存一致性協議

緩存一致性:緩存一致性機制整體來說,就是當某塊CPU對緩存中的數據進行操作了之后,會通知其他CPU放棄儲存在它們內部的緩存,或者從主內存中重新讀取。

緩存鎖的核心機制就是基于緩存一致性協議來實現的,即一個處理器的緩存回寫到內存會導致其他處理器的緩存無效,IA-32處理器和Intel 64處理器使用MESI實現緩存一致性協議。

緩存一致性是一個協議,不同處理器的具體實現會有所不同,MESI是一種比較常見的緩存一致性協議實現。

MESI協議

MESI協議是以緩存行的幾個狀態來命名的(全名是Modified、Exclusive、Share or Invalid)。該協議要求在每個緩存行上維護兩個狀態位,每個數據單位可能處于M、E、S和I這四種狀態之一,各種狀態含義如下:

  • M(Modified):被修改的。該狀態的數據,只在本CPU緩存中存在,其他CPU沒有。同時,對于內存中的值來說,是已經被修改了,但還沒更新到內存中去。也就是說緩存中的數據和內存中的數據不一致。
  • E(Exclusive):獨占的。該狀態的數據,只在本CPU緩存中存在,且并沒有被修改,與內存數據一致。
  • S(Share):共享的。該狀態的數據,在多個CPU緩存中同時存在,且與內存數據一致。
  • I(Invalid):無效的。本CPU中的這份緩存數據已經失效。

其中上述狀態隨著不同CPU的操作還會進行不停的變更:

一個處于M狀態的緩存行,必須時刻監聽所有試圖讀取該緩存行對應的主存地址的操作,如果監聽到,則必須在此操作執行前把其緩存行中的數據寫回CPU。

一個處于S狀態的緩存行,必須時刻監聽使該緩存行無效或者獨享該緩存行的請求,如果監聽到,則必須把其緩存行狀態設置為I。

一個處于E狀態的緩存行,必須時刻監聽其他試圖讀取該緩存行對應的主存地址的操作,如果監聽到,則必須把其緩存行狀態設置為S。

對于MESI協議,從CPU讀寫角度來說會遵循以下原則:

CPU讀數據:當CPU需要讀取數據時,如果其緩存行的狀態是I的,則需要從內存中讀取,并把自己狀態變成S,如果不是I,則可以直接讀取緩存中的值,但在此之前,必須要等待其他CPU的監聽結果,如其他CPU也有該數據的緩存且狀態是M,則需要等待其把緩存更新到內存之后,再讀取。

CPU寫數據:當CPU需要寫數據時,只有在其緩存行是M或者E的時候才能執行,否則需要發出特殊的RFO指令(Read Or Ownership,這是一種總線事務),通知其他CPU設置緩存無效(I),這種情況下性能開銷是相對較大的。在寫入完成后,修改其緩存狀態為M。

當引入總線鎖或緩存一致性協議之后,CPU、緩存、內存的結構變為下圖:

CPU-緩存-總線-內存

MESI協議帶來的問題

在上述MESI協議的交互過程中,我們已經可以看到在各個CPU之間存在大量的消息傳遞(監聽處理)。而緩存的一致性消息傳遞是需要時間的,這就使得切換時會產生延遲。一個CPU對緩存中數據的改變,可能需要獲得其他CPU的回執之后才能繼續進行,在這期間處于阻塞狀態。

Store Bufferes

等待確認的過程會阻塞處理器,降低處理器的性能。而且這個等待遠遠比一個指令的執行時間長的多。為了避免資源浪費,CPU又引入了存儲緩存(Store Bufferes)。

基于存儲緩存,CPU將要寫入內存數據先寫入Store Bufferes中,同時發送消息,然后就可以繼續處理其他指令了。當收到所有其他CPU的失效確認(Invalidate Acknowledge)時,數據才會最終被提交。

舉例說明一下Store Bufferes的執行流程:比如將內存中共享變量a的值由1修改為66。

第一步,CPU-0把a=66寫入Store Bufferes中,然后發送Invalid消息給其他CPU,無需等待其他CPU相應,便可繼續執行其他指令了。

store bufferes

第二步,當CPU-0收到其他所有CPU對Invalid通知的相應之后,再把Store Bufferes中的共享變量同步到緩存和主內存中。

store Bufferes

Store Forward(存儲轉發)

Store Bufferes的引入提升了CPU的利用效率,但又帶來了新的問題。在上述第一步中,Store Bufferes中的數據還未同步到CPU-0自己的緩存中,如果此時CPU-0需要讀取該變量a,緩存中的數據并不是最新的,所以CPU需要先讀取Store Bufferes中是否有值。如果有則直接讀取,如果沒有再到自己緩存中讀取,這就是所謂的”Store Forward“。

失效隊列

CPU將數據寫入Store Bufferes的同時還會發消息給其他CPU,由于Store Bufferes空間較小,且其他CPU可能正在處理其他事情,沒辦法及時回復,這個消息就會陷入等待。

為了避免接收消息的CPU無法及時處理Invalid失效數據的消息,造成CPU指令等待,就在接收CPU中添加了一個異步消息隊列。消息發送方將數據失效消息發送到這個隊列中,接收CPU返回已接收,發送方CPU就可以繼續執行后續操作了。而接收方CPU再慢慢處理”失效隊列“中的消息。

內存屏障

CPU經過上述的一系列優化,既保證了效率又確保了緩存的一致性,大多數情況下也是可以接受CPU基于Store Bufferes和失效隊列異步處理的短暫延遲的。

但在多線程的極端情況下,還是會產生緩存數據不一致的情況的。比如上述實例中,CPU-0修改數據,發消息給其他CPU,其他CPU消息隊列接收成功并返回。這時CPU-1正忙著處理其他業務,沒來得及處理消息隊列,而CPU-1處理的業務中恰好又用到了變量a,此時就會造成讀取到的a值為舊值。

這種因為CPU緩存優化導致后面的指令無法感知到前面指令的執行結果,看起來就像指令之間的執行順序錯亂了一樣,對于這種現象我們俗稱“CPU亂序執行”。

亂序執行是導致多線程下程序Bug的原因,解決方案很簡單:禁用CPU緩存優化。但大多數情況下的數據并不存在共享問題,直接禁用會導致整體性能下降,得不償失。于是就提供了針對多線程共享場景的解決機制:內存屏障機制。

使用內存屏障后,寫入數據時會保證所有指令都執行完畢,這樣就能保證修改過的數據能夠即時暴露給其他CPU。而讀取數據時,能夠保證所有“失效隊列”消息都消費完畢。然后,CPU根據Invalid消息判斷自己緩存狀態,正確讀寫數據。

CPU層面的內存屏障

CPU層面提供了三類內存屏障:

  • 寫屏障(Store Memory Barrier):告訴處理器在寫屏障之前將所有存儲在存儲緩存(store bufferes)中的數據同步到主內存。也就是說當看到Store Barrier指令,就必須把該指令之前所有寫入指令執行完畢才能繼續往下執行。
  • 讀屏障(Load Memory Barrier):處理器在讀屏障之后的讀操作,都在讀屏障之后執行。也就是說在Load屏障指令之后就能夠保證后面的讀取數據指令一定能夠讀取到最新的數據。
  • 全屏障(Full Memory Barrier):兼具寫屏障和讀屏障的功能。確保屏障前的內存讀寫操作的結果提交到內存之后,再執行屏障后的讀寫操作。

下面通過一段偽代碼來進行說明:

  1. public class Demo { 
  2.     int value; 
  3.     boolean isFinish; 
  4.  
  5.     void cpu0(){ 
  6.         value = 10; // S->I狀態,將value寫入store bufferes,通知其他CPU value緩存失效 
  7.         storeMemoryBarrier(); // 插入寫屏障,將value=10強制寫入主內存 
  8.         isFinish = true; // E狀態 
  9.     } 
  10.      
  11.     void cpu1(){ 
  12.         if (isFinish){ // true 
  13.             loadMemoryBarrier(); //插入讀屏障,強制cpu1從主內存中獲取最新數據 
  14.             System.out.println(value == 10); // true 
  15.         } 
  16.     } 
  17.  
  18.     void storeMemoryBarrier(){//寫屏障 
  19.     } 
  20.     void loadMemoryBarrier(){//讀屏障 
  21.     } 

上述實例中通過內存屏障防止了指令重排,能夠得到預期的結果。

總之,內存屏障的作用可以通過防止CPU亂序執行來保證共享數據在多線程下的可見性。那么,在JVM中是如何解決該問題的呢?也就是編程人員如何進行控制呢?這就涉及到我們要講的volatile關鍵字了。

Java內存模型

內存屏障解決了CPU緩存優化導致的指令執行的順序性和可見性問題,但不同的硬件系統提供的“內存屏障”指令又有所不同,作為開發人員也沒必要熟悉所有的內存屏障指令。而Java將不同的內存屏障指令進行了統一封裝,開發人員只需關注程序邏輯開發和內存屏障規范即可。

這套封裝解決方案的模型就是我們常說的Java內存模型(Java Memory Model),簡稱JMM。JMM最核心的價值便在于解決可見性和有序性,它是對硬件模型的抽象,定義了共享內存中多線程程序讀寫操作的行為規范。

這套規范通過限定對內存的讀寫操作從而保證指令的正確性,解決了CPU多級緩存、處理器優化、指令重排序導致的內存訪問問題,保證了并發場景下的可見性。

本質上,JMM是把硬件底層的問題抽象到了JVM層面,屏蔽了各個平臺的硬件差異,然后再基于CPU層面提供的內存屏障指令以及限制編譯器的重排序來解決并發問題的。

JMM抽象模型結構

JMM抽象模型中將內存分為主內存和工作內存:

  • 主內存:所有線程共享,存儲實例對象、靜態字段、數組對象等存儲在堆中的變量。
  • 工作內存:每個線程獨享,線程對變量的所有操作都必須在工作內存中進行。

線程是CPU調度的最小單位,線程之間的共享變量值的傳遞都必須通過主內存來完成。

JMM抽象模型結構圖如下:

JMM抽象模型

JMM內存模型簡單概述就是:

  • 所有變量存儲在主內存;
  • 每條線程擁有自己的工作內存,其中保存了主內存中線程使用到的變量的副本;
  • 線程不能直接讀寫主內存中的變量,所有操作均在工作內存中完成;

如果線程A需要與線程B進行通信,則線程A先把本地緩存中的數據更新到主內存,再由線程B從主內存中進行獲取。JMM通過控制主內存與每個線程的本地內存之間的交互,來為Java程序提供內存可見性保證。

編譯器指令重排

除了硬件層面的指令重排,Java編譯器為了提升性能,也會對指令進行重排。Java規范規定JVM線程內部維持順序化語義,即只要程序的最終結果與它順序化執行的結果相等,那么指令的執行順序可以與代碼順序不一致,此過程叫指令的重排序。

JVM能根據處理器特性(CPU多級緩存系統、多核處理器等)適當的對機器指令進行重排序,使機器指令能更符合CPU的執行特性,最大限度的發揮機器性能。

從源碼到最終執行示例圖:

指令重排序

其中2和3屬于CPU執行階段的重排序,1屬于編譯器階段的重排序。編譯器會遵守happens-before規則和as-if-serial語義的前提下進行指令重排。

happens-before規則:如果A happens-before B,且B happens-before C,則需要保證A happens-before C。

as-if-serial語義:不管怎么重排序(編譯器和處理器為了提高并行度),(單線程)程序的執行結果不能被改變。編譯器、Runtime和處理器都必須遵守as-if-serial語義。

對于處理器重排序,JMM要求Java編譯器在生成指令序列時,插入特定類型的內存屏障指令,來禁止特定類型的處理重排序。

JMM的內存屏障

上面了解了CPU的內存屏障分類,在JMM中把內存屏障分為四類:

  • LoadLoad Barriers:示例,Load1;LoadLoad;Load2,確保Load1數據的裝載先于Load2及所有后續指令的裝載;
  • StoreStore Barriers:示例,Store1;StoreStore;Store2,確保Store1數據對其他處理器可見(刷新到內存)先于Store2及所有后續存儲指令的存儲;
  • LoadStore Barriers:示例,Load1;LoadStore;Store2,確保Load1數據裝載先于Store2及所有后續存儲指令刷新到內存;
  • StoreLoad Barriers:示例,Store1;StoreLoad;Load2,確保Store1數據對其他處理器變得可見(刷新到內存)先于Load2及所有后續裝載指令的裝載。StoreLoad Barriers會使該屏障之前的所有內存訪問指令(存儲和裝載指令)完成之后,才執行該屏障之后的內存訪問指令。

其中,StoreLoad Barriers同時具有前3個的屏障的效果,但性能開銷很大。

為了實現volatile內存語義,JMM會分別限制這兩種類型的重排序類型。下圖是JMM針對編譯器制定的volatile重排序規則表。

JMM重排序

從圖中可以得出一個基本規則:

  • 當第二個操作是volatile寫時,不管第一個操作是什么,都不能重排序。這個規則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之后。
  • 當第一個操作是volatile讀時,不管第二個操作是什么,都不能重排序。這個規則確保volatile讀之后的操作不會被編譯器重排序到volatile讀之前。
  • 當第一個操作是volatile寫,第二個操作是volatile讀時,不能重排序。

為了實現volatile的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。對于編譯器來說,發現一個最優布置來最小化插入屏障的總數幾乎不可能。為此,JMM采取保守策略。下面是基于保守策略的JMM內存屏障插入策略:

  • 在每個volatile寫操作的前面插入一個StoreStore屏障。
  • 在每個volatile寫操作的后面插入一個StoreLoad屏障。
  • 在每個volatile讀操作的后面插入一個LoadLoad屏障。
  • 在每個volatile讀操作的后面插入一個LoadStore屏障。

保守策略下volatile寫插入內存屏障后生成的指令序列示意圖:

volatile寫屏障

保守策略下volatile讀插入內存屏障后生成的指令序列示意圖:

volatile讀內存屏障

JMM對volatile的特殊規則定義

JVM內存指令與volatile相關的操作有:

  • read(讀取):作用于主內存變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨后的load動作使用;
  • load(載入):作用于工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中;
  • use(使用):作用于工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作;
  • assign(賦值):作用于工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作;
  • store(存儲):作用于工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨后的write的操作;
  • write(寫入):作用于主內存的變量,它把store操作從工作內存中一個變量的值傳送到主內存的變量中;

在對volatile修飾的變量進行操作時,需滿足以下規則:

  • 規則1:線程對變量執行的前一個動作是load時才能執行use,反之只有后一個動作是use時才能執行load。線程對變量的read,load,use動作關聯,必須連續一起出現。這保證了線程每次使用變量時都需要從主存拿到最新的值,保證了其他線程修改的變量本線程能看到。
  • 規則2:線程對變量執行的前一個動作是assign時才能執行store,反之只有后一個動作是store時才能執行assign。線程對變量的assign,store,write動作關聯,必須連續一起出現。這保證了線程每次修改變量后都會立即同步回主內存,保證了本線程修改的變量其他線程能看到。
  • 規則3:有線程T,變量V、變量W。假設動作A是T對V的use或assign動作,P是根據規則2、3與A關聯的read或write動作;動作B是T對W的use或assign動作,Q是根據規則2、3與B關聯的read或write動作。如果A先與B,那么P先與Q。這保證了volatile修飾的變量不會被指令重排序優化,代碼的執行順序與程序的順序相同。

volatile實例及分析

通過上面的分析關于volatile關鍵詞的來源,以及被它修飾的變量的可見性和有序性都從理論層面講解清楚了。下面看一個可見性的實例。

示例代碼如下:

  1. public class VolatileTest { 
  2.  
  3.  private boolean initFlag = false
  4.  
  5.  public static void main(String[] args) throws InterruptedException { 
  6.   VolatileTest sample = new VolatileTest(); 
  7.   Thread threadA = new Thread(sample::refresh, "threadA"); 
  8.  
  9.   Thread threadB = new Thread(sample::load"threadB"); 
  10.  
  11.   threadB.start(); 
  12.   Thread.sleep(2000); 
  13.   threadA.start(); 
  14.  } 
  15.  
  16.  public void refresh() { 
  17.   this.initFlag = true
  18.   System.out.println("線程:" + Thread.currentThread().getName() + ":修改共享變量initFlag"); 
  19.  } 
  20.  
  21.  public void load() { 
  22.   int i = 0; 
  23.   while (!initFlag) { 
  24.   } 
  25.   System.out.println("線程:" + Thread.currentThread().getName() + "當前線程嗅探到initFlag的狀態的改變" + i); 
  26.  } 

根據上面的理論知識,先猜測一下線程先后打印出的內容是什么?先打印”線程threadA修改共享變量initFlag“,然后打印”線程threadB當前線程嗅探到initFlag的狀態的改變0“?

當真正執行程序時,會發現整個線程阻塞在while循環處,并未打印出第2條內容。此時JMM操作如下圖:

thread-without-volatile

雖然線程A中將initFlag改為了true并且最終會同步回主內存,但是線程B中循環讀取的initFlag一直都是從工作內存讀取的,所以會一直進行死循環無法退出。

當對變量initFlag添加了volatile修飾之后:

  1. public class VolatileTest { 
  2.  
  3.  private volatile boolean initFlag = false
  4.  //... 

JMM操作如下圖:

thread-with-volatile

添加了volatile修飾之后,兩句日志都會被打印出來。這是因為添加volatile關鍵字后,就會有lock指令,使用緩存一致性協議,線程B中會一直嗅探initFlag是否被改變,線程A修改initFlag后會立即同步回主內存,同時通知線程B將緩存行狀態改為I(無效狀態),重新從主內存讀取。

volatile無法保證原子性

volatile雖然保證了共享變量的可見性和有序性,但并不能夠保證原子性。

以常見的自增操作(count++)為例來進行說明,通常自增操作底層是分三步的:

  • 第一步:獲取變量count;
  • 第二步:count加1;
  • 第三步:回寫count。

我們來分析一下在這個過程中會有的線程安全問題:

第一步,線程A和B同時獲得count的初始值,這一步沒什么問題;

第二步,線程A自增count并回寫,但線程B此時也已經拿到count,不會再去拿線程A回寫的值,因此對原始值進行自增并回寫,這就導致了線程安全的問題。有人可能要問了,線程A自增之后不是應該通知其他CPU緩存失效嗎,并重新load嗎?我們要知道,重新獲取的前提操作是讀,在線程A回寫時,線程B已經拿到了count的值,并不存在再次讀的場景。也就是說,線程B的緩存行的確會失效,但線程B中count值已經運行在加法指令中,不存在需要再次從緩存行讀的場景。

volatile關鍵字只保證可見性,所以在以下情況中,需要使用鎖來保證原子性:

  • 運算結果依賴變量的當前值,并且有不止一個線程在修改變量的值。
  • 變量需要與其他狀態變量共同參與不變約束

所以,想要使用volatile變量提供理想的線程安全,必須同時滿足兩個條件:

  • 對變量的寫操作不依賴于當前值。
  • 該變量沒有包含在具有其他變量的不變式中。

也就是說被修飾的變量值獨立于任何程序的狀態,包括變量的當前狀態。

volatile適用場景

狀態標志

使用一個布爾狀態標志,用于指示發生了一個重要的一次性事件,例如完成初始化或請求停機。

  1. volatile boolean shutdownRequested; 
  2.   
  3. ... 
  4.   
  5. public void shutdown() {  
  6.     shutdownRequested = true;  
  7.   
  8. public void doWork() {  
  9.     while (!shutdownRequested) {  
  10.         // do stuff 
  11.     } 

線程1執行doWork()的過程中,線程2可能調用了shutdown,所以boolean變量必須是volatile。

這種狀態標記的一個公共特性是:通常只有一種狀態轉換;shutdownRequested 標志從false 轉換為true,然后程序停止。這種模式可以擴展到來回轉換的狀態標志,但是只有在轉換周期不被察覺的情況下才能擴展(從false 到true,再轉換到false)。此外,還需要某些原子狀態轉換機制,例如原子變量。

一次性安全發布

在缺乏同步的情況下,可能會遇到某個對象引用的更新值(由另一個線程寫入)和該對象狀態的舊值同時存在。

這種場景在著名的雙重檢查鎖定(double-checked-locking)中會出現:

  1. //注意volatile! 
  2. private volatile static Singleton instace;    
  3.    
  4. public static Singleton getInstance(){    
  5.     //第一次null檢查      
  6.     if(instance == null){             
  7.         synchronized(Singleton.class) {    //1      
  8.             //第二次null檢查        
  9.             if(instance == null){          //2   
  10.                 instance = new Singleton();//3   
  11.             }   
  12.         }            
  13.     }   
  14.     return instance;         

其中第3步中實例化Singleton分多步執行(分配內存空間、初始化對象、將對象指向分配的內存空間),某些編譯器為了性能原因,會將第二步和第三步進行重排序(分配內存空間、將對象指向分配的內存空間、初始化對象)。這樣,某個線程可能會獲得一個未完全初始化的實例。

獨立觀察(independent observation)

場景:定期 “發布” 觀察結果供程序內部使用。比如,傳感器感知溫度,一個線程每隔幾秒讀取一次傳感器,并更新當前的volatile修飾變量。其他線程可以讀取這個變量,隨時看到最新溫度。

另一種場景就是應用程序搜集統計信息。比如記錄最后一次登錄的用戶名,反復使用lastUser引用來發布值,以供其他程序使用。

  1. public class UserManager { 
  2.     public volatile String lastUser; //發布的信息 
  3.   
  4.     public boolean authenticate(String user, String password) { 
  5.         boolean valid = passwordIsValid(userpassword); 
  6.         if (valid) { 
  7.             User u = new User(); 
  8.             activeUsers.add(u); 
  9.             lastUser = user
  10.         } 
  11.         return valid; 
  12.     } 
  13. }  

“volatile bean” 模式

volatile bean 模式的基本原理是:很多框架為易變數據的持有者(例如 HttpSession)提供了容器,但是放入這些容器中的對象必須是線程安全的。在 volatile bean 模式中,JavaBean 的所有數據成員都是 volatile 類型的,并且 getter 和 setter 方法必須非常普通——即不包含約束。

  1. @ThreadSafe 
  2. public class Person { 
  3.     private volatile String firstName; 
  4.     private volatile String lastName; 
  5.     private volatile int age; 
  6.   
  7.     public String getFirstName() { return firstName; } 
  8.     public String getLastName() { return lastName; } 
  9.     public int getAge() { return age; } 
  10.   
  11.     public void setFirstName(String firstName) {  
  12.         this.firstName = firstName; 
  13.     } 
  14.   
  15.     public void setLastName(String lastName) {  
  16.         this.lastName = lastName; 
  17.     } 
  18.   
  19.     public void setAge(int age) {  
  20.         this.age = age; 
  21.     } 

開銷較低的“讀-寫鎖”策略

如果讀操作遠遠超過寫操作,可以結合使用內部鎖和 volatile 變量來減少公共代碼路徑的開銷。

如下線程安全的計數器代碼,使用 synchronized 確保增量操作是原子的,并使用 volatile 保證當前結果的可見性。如果更新不頻繁的話,該方法可實現更好的性能,因為讀路徑的開銷僅僅涉及 volatile 讀操作,這通常要優于一個無競爭的鎖獲取的開銷。

  1. @ThreadSafe 
  2. public class CheesyCounter { 
  3.     // Employs the cheap read-write lock trick 
  4.     // All mutative operations MUST be done with the 'this' lock held 
  5.     @GuardedBy("this") private volatile int value; 
  6.   
  7.     //讀操作,沒有synchronized,提高性能 
  8.     public int getValue() {  
  9.         return value;  
  10.     }  
  11.   
  12.     //寫操作,必須synchronized。因為x++不是原子操作 
  13.     public synchronized int increment() { 
  14.         return value++; 
  15.     } 

使用鎖進行有變化的操作,使用volatile進行只讀操作。volatile允許多個線程同時執行讀操作。

小結

本文先從硬件層面分析CPU的處理機制,為了優化CPU引入了緩存,為了更進一步優化引入了Store Bufferes,而Store Bufferes導致了緩存一致性問題。于是有了總線鎖和緩存一致性協議(EMSI實現),從而有了CPU的內存屏障機制。

而CPU的內存屏障反映在Java編程語言中,有了Java內存模型(JMM),JMM屏蔽了底層硬件的不同,提供了統一的操作,進而編程人員可以通過volatile關鍵字來解決共享變量的可見性和順序性。

緊接著,通過實例演示了volatile的作用以及它不具有線程安全保證的反面案例。最后,舉例說明volatile的運用場景。

想必通過這篇文章,你已經徹底弄懂了volatile相關的知識了吧?來,關注一波。

 

責任編輯:武曉燕 來源: 程序新視界
相關推薦

2009-12-07 17:20:29

PHP stdClas

2010-04-21 14:56:23

Unix 線程

2009-02-20 10:59:21

Vista幫助系統使用技巧

2010-04-27 17:06:16

AIX vmstat

2009-10-23 15:30:53

無線接入技術

2011-07-25 16:25:47

2019-10-21 15:30:54

JS技巧前端

2011-07-08 13:56:00

域控制器服務器

2009-07-01 17:58:20

JSP

2013-04-07 10:15:34

2013-04-10 10:39:57

2021-07-12 07:08:52

Spring Boot集成框架

2024-01-26 16:28:28

C++動態內存開發

2009-12-01 11:33:03

PHP判斷字符串的包含

2012-02-04 14:56:52

JP1數據中心

2012-01-10 10:05:47

文件目錄訪問控制UGO

2010-05-27 13:32:36

IIS服務安全認證

2011-08-23 18:30:59

MySQLTIMESTAMP

2016-10-08 12:46:08

Linux監控限制

2021-08-26 06:58:14

CookieSession應用
點贊
收藏

51CTO技術棧公眾號

蜜桃视频在线网站| 九九九在线观看| 日韩一区二区三区精品视频第3页| 亚洲精品写真福利| 99在线影院| 亚洲综合一二三| 精品久久久久久久久久久下田| 欧美日韩一区精品| 97在线免费视频观看| 手机看片一区二区| 日本欧美一区二区在线观看| 中文字幕亚洲激情| 日本一二三区在线| 精精国产xxxx视频在线播放| 国产亚洲成aⅴ人片在线观看| 国产欧美日韩中文| 亚洲一区欧美在线| 99久久99久久精品国产片桃花| 日韩精品一区二区三区在线播放| 在线观看成人一级片| 一级特黄色大片| 中文无码久久精品| 亚洲欧洲国产一区| 欧美熟妇精品一区二区| 色成人免费网站| 亚洲韩国一区二区三区| 亚洲成色www久久网站| 亚洲第一页在线观看| 蜜臀国产一区二区三区在线播放| 欧美日韩成人免费| 免费成人深夜天涯网站| 电影一区二区在线观看| 欧美日韩大陆在线| 欧美黄色免费影院| 精品精品导航| 国产精品每日更新在线播放网址| 久久视频在线观看中文字幕| 99久久婷婷国产一区二区三区| 噜噜噜躁狠狠躁狠狠精品视频| 免费97视频在线精品国自产拍| 少妇av片在线观看| 日韩美女毛片| 精品av久久707| 色偷偷中文字幕| 久久av影院| 在线精品视频一区二区三四| 怡红院av亚洲一区二区三区h| 在线观看小视频| 国产精品久久久久影院| 日韩一区二区三区资源| 免费在线超碰| 久久久久久免费| 麻豆91蜜桃| 日色在线视频| 久久综合色8888| 九色91在线视频| 日本高清视频在线| 播五月开心婷婷综合| 国产精品日韩一区二区| 日本免费高清一区二区| 精品免费囯产一区二区三区 | 久久久久亚洲av片无码下载蜜桃| 91久久国产| 久久久999成人| 国产探花在线视频| 偷拍欧美精品| 久久久国产精品一区| 色老板免费视频| 亚洲最大av| 欧美国产亚洲精品久久久8v| 久久久一区二区三区四区| 欧美午夜在线视频| 久久理论片午夜琪琪电影网| 豆国产97在线 | 亚洲| 亚洲网址在线| 456国产精品| 51国产偷自视频区视频| 日韩av网站在线观看| 国产精品无av码在线观看| 在线免费看av片| 激情图片小说一区| 国产高清一区二区三区| 无码h黄肉3d动漫在线观看| 91在线视频18| 日韩高清dvd| 久久五月精品| 亚洲成人av一区二区三区| 欧美一级在线看| 岛国精品在线| 精品欧美久久久| 手机av免费看| 国产精品99一区二区三区| 欧美激情精品久久久久久蜜臀 | 色综合久久天天| 在线免费视频a| 欧美午夜网站| 日韩经典中文字幕在线观看| 亚洲一级片在线播放| 欧美日韩岛国| 国产va免费精品高清在线| 国产精品毛片一区二区在线看舒淇| 国产乱码精品一区二区三区五月婷| 国产精品久久久久久久久婷婷| 户外极限露出调教在线视频| 亚洲视频在线一区二区| 日韩中文字幕在线视频观看| 欧美一级网址| 亚洲国内精品视频| 日日操免费视频| 亚洲精品影院在线观看| 国产精品视频26uuu| 男人天堂网在线视频| 欧美激情在线看| 日韩专区第三页| 欧美极品免费| 亚洲成色777777女色窝| 成年人视频软件| 在线欧美一区| 96sao精品视频在线观看| 日本福利片高清在线观看| 亚洲三级视频在线观看| 国产精品人人妻人人爽人人牛| 亚洲精品在线国产| 中文字幕9999| 欧美一级视频免费观看| 国产成人综合网站| 亚洲图色在线| 欧美人体一区二区三区| 精品盗摄一区二区三区| 一本一本久久a久久| 久久婷婷麻豆| 精品国产综合| 日本三级在线观看网站| 91超碰这里只有精品国产| 魔女鞋交玉足榨精调教| 亚洲国产二区| 91亚洲精品丁香在线观看| 92国产在线视频| 色综合久久久久网| 水蜜桃av无码| 伊人久久综合| 亚洲一区免费网站| 自拍视频在线| 欧美性色黄大片| 中国毛片在线观看| 亚洲中午字幕| 久久伊人一区| 午夜不卡影院| 日韩精品中文字幕在线| 日本五十路女优| 国产v日产∨综合v精品视频| 国产激情在线看| 国产精品777777在线播放| 中文字幕在线视频日韩| 国产一级精品毛片| 中文字幕欧美日本乱码一线二线| 欧美激情精品久久久久久小说| 亚洲aa在线| 热99在线视频| 激情小说 在线视频| 欧美日韩在线观看视频| av网站有哪些| 免费日韩视频| 欧美日韩综合精品| 四虎影视4hu4虎成人| 亚洲午夜小视频| 最新国产中文字幕| 中文字幕一区二区三区精华液| 在线观看av网页| 久久一区二区中文字幕| 国产欧美精品久久久| 成人午夜在线影视| 欧美一级专区免费大片| 青青青在线视频| av在线播放不卡| 国产超级av在线| 欧美一级本道电影免费专区| 国产一区二区丝袜高跟鞋图片| 日本中文字幕在线观看| 日韩视频不卡中文| 精品无码人妻一区二区三| 久久综合久久综合亚洲| 草草草在线视频| 99精品视频精品精品视频| av在线不卡一区| 亚洲欧洲高清| 日韩一区二区三区国产| 亚洲精品一区二区三区不卡| 欧美日韩国产麻豆| 欧美日韩中文字幕视频| 国产老妇另类xxxxx| 每日在线更新av| 日韩成人综合| 国产精品sss| 欧美最新精品| 欧美疯狂xxxx大交乱88av| 欧美xo影院| 亚洲成在人线免费| 日本黄色片在线播放| 日韩精品亚洲专区| 伊人久久在线观看| 亚洲综合图色| 亚洲一区二区三区在线视频| 忘忧草在线影院两性视频| 日韩小视频在线| 少妇av一区二区| 欧美肥妇毛茸茸| 日韩欧美一级视频| 亚洲私人黄色宅男| 国产熟妇久久777777| 国产一区二区不卡| 成人免费无码av| 伊人影院久久| 国产又大又长又粗又黄| 一区二区导航| 成人自拍爱视频| 日本在线一区二区| 日本在线观看天堂男亚洲| 欧美另类tv| 色偷偷偷综合中文字幕;dd| 天堂在线中文资源| 精品欧美一区二区三区精品久久 | 欧美日韩免费| 亚洲精品国产一区| 九热爱视频精品视频| 99国精产品一二二线| 99久久er| 日本免费久久高清视频| 不卡一本毛片| 久99久在线视频| 免费a级在线播放| 在线不卡国产精品| 青春有你2免费观看完整版在线播放高清 | 懂色av中文字幕一区二区三区| 色一情一乱一伦一区二区三区日本| 红桃视频亚洲| 91大学生片黄在线观看| 久久精品国内一区二区三区水蜜桃| 欧美日韩最好看的视频| 网友自拍一区| 在线影院国内精品| 野外做受又硬又粗又大视频√| sdde在线播放一区二区| 国产欧美日本在线| 欧美久久一区二区三区| 国产一区二区在线免费视频| 精品欧美一区二区三区在线观看| 97免费在线视频| 51精品视频| 久久久久九九九九| 中文在线观看免费| 久久久av亚洲男天堂| 麻豆视频网站在线观看| 日韩视频免费中文字幕| 1pondo在线播放免费| 中文字幕亚洲欧美日韩高清| av在线中文| 影音先锋日韩在线| 亚洲综合精品伊人久久| 天堂久久av| 国产精品av一区| 欧美日韩一区二区三区四区不卡| 极品日韩久久| 蜜桃精品wwwmitaows| 欧美三级网色| 精品视频日韩| 一区二区国产日产| 欧美在线免费| 日韩精品一区在线视频| 亚洲视频www| 欧美精品第三页| 久久 天天综合| 爱情岛论坛亚洲自拍| 成人在线视频一区| 欧美精品黑人猛交高潮| 国产午夜亚洲精品理论片色戒| 丰满的亚洲女人毛茸茸| 亚洲男人的天堂在线观看| 国产精品suv一区二区69| 欧美日韩一二三四五区| 国产女优在线播放| 欧美一区二区三区人| 刘玥91精选国产在线观看| 亚洲精品自拍偷拍| 在线免费黄色| 久久久久久久久久久网站| 美脚恋feet久草欧美| 国产精自产拍久久久久久蜜| 1204国产成人精品视频| 欧美xxxx黑人又粗又长密月| 欧美freesextv| 久无码久无码av无码| 日韩精品午夜视频| wwwxx日本| 国产精品系列在线| 精品少妇久久久久久888优播| 一本一道综合狠狠老| 国产老女人乱淫免费| 日韩精品日韩在线观看| 免费av在线网址| 91大神福利视频在线| 欧美伊人亚洲伊人色综合动图| 国产乱码精品一区二区三区中文| 青青草国产成人a∨下载安卓| 亚洲最新免费视频| 国产欧美一级| 妖精视频在线观看| 欧美激情中文字幕| 精品少妇一区二区三区| 黄视频在线观看免费| 欧美精品做受xxx性少妇| 日韩成人动漫| 国产网站一区二区| 波多野结衣办公室33分钟| 亚洲老司机在线| 中文字幕 国产精品| 亚洲成人亚洲激情| 精品51国产黑色丝袜高跟鞋| 欧美一区亚洲一区| 中文字幕亚洲在线观看| 亚洲亚洲精品三区日韩精品在线视频| 亚洲人成在线影院| 一级网站在线观看| 中文字幕一区二区三区蜜月| 在线观看 亚洲| 国产福利一区二区三区视频在线| 三级男人添奶爽爽爽视频| 亚洲精品乱码久久久久久日本蜜臀| 天天爽夜夜爽人人爽| 精品国产123| 超碰在线无需免费| 国产欧美久久久久久| 免费看成人哺乳视频网站| 人妻少妇精品无码专区二区| 国产激情一区二区三区桃花岛亚洲| 日本视频在线免费| 欧美色国产精品| 高清毛片在线看| 日本精品视频在线| 奇米777国产一区国产二区| 日韩美女爱爱视频| 国产成人av电影| 国产1区2区3区4区| 欧美一级二级三级蜜桃| 麻豆av在线免费看| 成人啪啪免费看| 99re久久最新地址获取| 天美星空大象mv在线观看视频| 26uuu精品一区二区在线观看| 日本一本高清视频| 亚洲国内精品视频| 秋霞伦理一区| 久久久福利视频| 毛片一区二区| jizz中文字幕| 欧美日韩一级黄| 黄页视频在线播放| 97操在线视频| 影音先锋亚洲一区| 国产老熟女伦老熟妇露脸| 精品福利樱桃av导航| 美女毛片在线看| 国产精品入口日韩视频大尺度 | 精品国产乱码久久久久久88av| 亚洲午夜在线| 自拍视频一区二区| 色香蕉久久蜜桃| 成年人在线看| 成人激情电影一区二区| 午夜国产一区| 日本一区二区在线观看视频| 精品成人国产在线观看男人呻吟| 蜜桃免费在线| 成人一区二区电影| 激情综合久久| 亚洲性猛交xxxx乱大交| 欧美日韩国产色站一区二区三区| 好吊日视频在线观看| 97在线资源站| 99成人精品| 国产破处视频在线观看| 欧美一级黄色大片| 九九精品调教| 欧美日韩精品不卡| 精品一区二区av| 五月婷婷激情网| 中文字幕日韩欧美精品在线观看| 懂色av蜜桃av| 99久久精品久久久久久清纯| 青青国产在线观看| 中文字幕精品一区二区精品| www.久久99| 霍思燕三级露全乳照| 亚洲狠狠婷婷| 极品粉嫩小仙女高潮喷水久久 | 亚洲免费高清视频| 欧美成人毛片| 欧美精品日日鲁夜夜添| 七七成人影院| 日韩av电影免费播放|