国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

(七)Volatile的作用及原理

marek / 1280人閱讀

摘要:文章簡介分析的作用以及底層實現原理,這也是大公司喜歡問的問題內容導航的作用什么是可見性源碼分析的作用在多線程中,和都起到非常重要的作用,是通過加鎖來實現線程的安全性。而的主要作用是在多處理器開發中保證共享變量對于多線程的可見性。

文章簡介

分析volatile的作用以及底層實現原理,這也是大公司喜歡問的問題

內容導航

volatile的作用

什么是可見性

volatile源碼分析

volatile的作用

在多線程中,volatile和synchronized都起到非常重要的作用,synchronized是通過加鎖來實現線程的安全性。而volatile的主要作用是在多處理器開發中保證共享變量對于多線程的可見性。
可見性的意思是,當一個線程修改一個共享變量時,另外一個線程能讀取到修改以后的值。接下來通過一個簡單的案例來演示可見性問題

public class VolatileDemo {
    private /*volatile*/ static boolean stop=false; //添加volatile修飾和不添加volatile修飾的演示效果
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            int i=0;
            while(!stop){
                i++;
            }
        });
        thread.start();
        System.out.println("begin start thread");
        Thread.sleep(1000);
        stop=true;
    }
}

定義一個共享變量 stop

在main線程中創建一個子線程 thread,子線程讀取到 stop的值做循環結束的條件

main線程中修改stop的值為 true

當 stop沒有增加volatile修飾時,子線程對于主線程的 stop=true的修改是不可見的,這樣將導致子線程出現死循環

當 stop增加了volatile修飾時,子線程可以獲取到主線程對于 stop=true的值,子線程while循環條件不滿足退出循環

增加volatile關鍵字以后,main線程對于共享變量 stop值的更新,對于子線程 thread可見,這就是volatile的作用

這段代碼有些人測試不出效果,是因為JVM沒有優化導致的,在cmd控制臺輸入java -version,如果顯示的是 JavaHotSpot(TM)ServerVM,就能正常演示,如果是 JavaHotSpot(TM)ClientVM,需要設置成 Server模式

什么是可見性,以及volatile是如何保證可見性的呢?

什么是可見性

在并發編程中,線程安全問題的本質其實就是 原子性、有序性、可見性;接下來主要圍繞這三個問題進行展開分析其本質,徹底了解可見性的特性

原子性 和數據庫事務中的原子性一樣,滿足原子性特性的操作是不可中斷的,要么全部執行成功要么全部執行失敗

有序性 編譯器和處理器為了優化程序性能而對指令序列進行重排序,也就是你編寫的代碼順序和最終執行的指令順序是不一致的,重排序可能會導致多線程程序出現內存可見性問題

可見性 多個線程訪問同一個共享變量時,其中一個線程對這個共享變量值的修改,其他線程能夠立刻獲得修改以后的值

為了徹底了解這三個特性,我們從兩個層面來分析,第一個層面是硬件層面、第二個層面是JMM層面

從硬件層面分析三大特性

原子性、有序性、可見性這些問題,我們可以認為是基于多核心CPU架構下的存在的問題。因為在單核CPU架構下,所有的線程執行都是基于CPU時間片切換,所以不存在并發問題 (在IntelPentium4開始,引入了超線程技術,也就是一個CPU核心模擬出2個線程的CPU,實現多線程并行)。

CPU高速緩存

線程設計的目的是充分利用CPU達到實時性的效果,但是很多時候CPU的計算任務還需要和內存進行交互,比如讀取內存中的運算數據、將處理結果寫入到內存。在理想情況下,存儲器應該是非常快速的執行一條指令,這樣CPU就不會受到存儲器的限制。但目前技術無法滿足,所以就出現了其他的處理方式。

存儲器頂層是CPU中的寄存器,存儲容量小,但是速度和CPU一樣快,所以CPU在訪問寄存器時幾乎沒有延遲;接下來就是CPU的高速緩存;最后就是內存。

高速緩存從下到上越接近CPU訪問速度越快,同時容量也越小?,F在的大部分處理器都有二級或者三級緩存,分別是L1/L2/L3, L1又分為L1-d的數據緩存和L1-i的指令緩存。其中L3緩存是在多核CPU之間共享的。

原子性

在多核CPU架構下,在同一時刻對同一共享變量執行 decl指令(遞減指令,相當于i--,它分為三個過程:讀->改->寫,這個指令涉及到兩次內存操作,那么在這種情況下i的結果是無法預測的。這就是原子性問題

處理器如何解決原子性問題呢?

其實這個問題稍微提煉一下,無非就是多線程并行訪問同一個共享資源的時候的原子性問題,如果把問題放大到分布式架構里面,這個問題的解決方法就是鎖。所以在CPU層面,提供了兩種鎖的機制來保證原子性

總線鎖

如果多個處理器同時對同一共享變量進行 decl指令操作,那這個操作一定不是原子的,也就是執行的結果和預期結果不一致。如下圖所示,我們期望的結果是3,但是有可能結果是2

如果要解決這個問題,就需要是的CPU0在更新共享變量時,CPU1就不能操作緩存了該共享變量內存地址的緩存,所以處理器提供了總線鎖來解決問題,處理器會提供一個LOCK#信號,當一個處理器在總線上輸出這個信號時,其他處理器的請求會被阻塞,那么該處理器就可以獨占共享內存

總線鎖有一個弊端,總線鎖相當于使得多個CPU由并行執行變成了串行,使得CPU的性能嚴重下降,所以在P6系列以后的處理器中,引入了緩存鎖。
緩存鎖

我們只需要保證 多個線程操作同一個被緩存的共享數據的原子性就行,所以只需要鎖定被緩存的共享對象即可。所謂緩存鎖是指被緩存在處理器中的共享數據,在Lock操作期間被鎖定,那么當被修改的共享內存的數據回寫到內存時,處理器不在總線上聲明LOCK#信號,而是修改內部的內存地址,并通過 緩存一致性機制來保證操作的原子性。

什么是緩存一致性呢?
所謂緩存一致性,就是多個CPU核心中緩存的同一共享數據的數據一致性,而(MESI)使用比較廣泛的緩存一致性協議。MESI協議實際上是表示緩存的四種狀態
M(Modify) 表示共享數據只緩存在當前CPU緩存中,并且是被修改狀態,也就是緩存的數據和主內存中的數據不一致
E(Exclusive) 表示緩存的獨占狀態,數據只緩存在當前CPU緩存中,并且沒有被修改
S(Shared) 表示數據可能被多個CPU緩存,并且各個緩存中的數據和主內存數據一致
I(Invalid) 表示緩存已經失效

每個CPU核心不僅僅知道自己的讀寫操作,也會監聽其他Cache的讀寫操作
CPU的讀取會遵循幾個原則

如果緩存的狀態是I,那么就從內存中讀取,否則直接從緩存讀取

如果緩存處于M或者E的CPU 嗅探到其他CPU有讀的操作,就把自己的緩存寫入到內存,并把自己的狀態設置為S

只有緩存狀態是M或E的時候,CPU才可以修改緩存中的數據,修改后,緩存狀態變為M

可見性

CPU高速緩存以及指令重排序都會造成可見性問題,接下來從兩個角度來分析

MESI優化帶來的可見性問題

前面說過MESI協議,也就是緩存一致性協議。這個協議存在一個問題,就是當CPU0修改當前緩存的共享數據時,需要發送一個消息給其他緩存了相同數據的CPU核心,這個消息傳遞給其他CPU核心以及收到消息完成各自緩存狀態的切換這個過程中,CPU會等待所有緩存響應完成,這樣會降低處理器的性能。為了解決這個問題,引入了 StoreBufferes存儲緩存。

處理器把需要寫入到主內存中的值先寫入到存儲緩存中,然后繼續去處理其他指令。當所有的CPU核心返回了失效確認時,數據才會被最終提交。但是這種優化又會帶來另外的問題。
如果某個CPU嘗試將其他CPU占有的共享數據寫入到內存,消息提交給store buffer以后,當前CPU繼續做其他事情,而如果后面的指令依賴于這個被寫入內存的最新數據(由于store buffer還沒有寫入到內存),就會產生可見性問題(也就是值還沒有更新到內存中,這個時候讀取到的共享數據的值是錯誤的)。

Store Bufferes帶來的CPU內存的亂序訪問導致的可見性問題

Store Bufferes中的數據何時寫入到內存中是不確定的,那么意味著這個過程的執行順序也是不確定的,比如下面這個例子
exeToCPU0和exeToCPU1分別在兩個獨立的cpu核心上執行,假如CPU0 緩存了 isFinish這個共享變量,并且狀態為(E->獨占),而value可能是(S共享狀態被其他CPU核心修改以后變為I(失效狀態)。
這種情況下value的緩存數據變更路徑為, value將失效狀態需要響應給觸發緩存更新的CPU核心,接著該CPU將 StoreBufferes寫入到內存,這就會導致value會比isFinish更遲的拋棄存儲緩存。那么就可能出現CPU1讀取到了isFinish的值為true,而value的值不等于10的情況。
這種CPU的內存亂序訪問,會帶來可見性問題。

value = 3;
void exeToCPU0(){
  value = 10;
  isFinsh = true;
}
void exeToCPU1(){
  if(isFinsh){
    assert value == 10;
  }
}
CPU層面的內存屏障

什么是內存屏障?從前面的內容基本能有一個初步的猜想,內存屏障就是將 store bufferes中的指令寫入到內存,從而使得其他訪問同一共享內存的線程的可見性。
X86的memory barrier指令包括lfence(讀屏障) sfence(寫屏障) mfence(全屏障)

Store Memory Barrier(寫屏障) 告訴處理器在寫屏障之前的所有已經存儲在存儲緩存(store bufferes)中的數據同步到主內存,簡單來說就是使得寫屏障之前的指令的結果對屏障之后的讀或者寫是可見的
Load Memory Barrier(讀屏障) 處理器在讀屏障之后的讀操作,都在讀屏障之后執行。配合寫屏障,使得寫屏障之前的內存更新對于讀屏障之后的讀操作是可見的
Full Memory Barrier(全屏障) 確保屏障前的內存讀寫操作的結果提交到內存之后,再執行屏障后的讀寫操作

有了內存屏障以后,對于上面這個例子,我們可以這么來改,從而避免出現可見性問題

value = 3;
void exeToCPU0(){
  value = 10;
  storeMemoryBarrier(); //這個是一個偽代碼,插入一個寫屏障,使得value=10這個值強制寫入到主內存中
  isFinsh = true;
}
void exeToCPU1(){
  if(isFinsh){
    loadMemoryBarrier();//偽代碼,插入一個讀屏障,使得cpu1從主內存中獲得最新的數據
    assert value == 10;
  }
}
總的來說,內存屏障的作用可以通過防止CPU對內存的亂序訪問來保證共享數據在多線程并行執行下的可見性
有序性

有序性簡單來說就是程序代碼執行的順序是否按照我們編寫代碼的順序執行,一般來說,為了提高性能,編譯器和處理器會對指令做重排序,重排序分3類

編譯器優化重排序,在不改變單線程程序語義的前提下,改變代碼的執行順序

指令集并行的重排序,對于不存在數據依賴的指令,處理器可以改變語句對應指令的執行順序來充分利用CPU資源

內存系統的重排序,也就是前面說的CPU的內存亂序訪問問題3.

也就是說,我們編寫的源代碼到最終執行的指令,會經過三種重排序

有序性會帶來可見性問題,所以可以通過內存屏障指令來進制特定類型的處理器重排序
從JMM層面解決線程并發問題

從硬件層面的分析了解到原子性、有序性、可見性的本質以后,知道硬件層面針對這三個問題的解決辦法,原子性是通過總線鎖或緩存鎖來實現,而有序性和可見性可以通過內存屏障來解決。那么在軟件層面,如何解決原子性、有序性、可見性問題呢?答案就是: JMM(JavaMemoryModel)內存模型

硬件層面的原子性、有序性、可見性在不同的CPU架構和操作系統中的實現可能都不一樣,而Java語言的特性是 write once,run anywhere,意味著JVM層面需要屏蔽底層的差異,因此在JVM規范中定義了JMM。

JMM屬于語言級別的抽象內存模型,可以簡單理解為對硬件模型的抽象,它定義了共享內存中多線程程序讀寫操作的行為規范,也就是在虛擬機中將共享變量存儲到內存以及從內存中取出共享變量的底層細節。
通過這些規則來規范對內存的讀寫操作從而保證指令的正確性,它解決了CPU多級緩存、處理器優化、指令重排序導致的內存訪問問題,保證了并發場景下的可見性。
需要注意的是,JMM并沒有限制執行引擎使用處理器的寄存器或者高速緩存來提升指令執行速度,也沒有限制編譯器對指令進行重排序,也就是說在JMM中,也會存在緩存一致性問題和指令重排序問題。只是JMM把底層的問題抽象到JVM層面,再基于CPU層面提供的內存屏障指令,以及限制編譯器的重排序來解決并發問題

Java內存模型定義了線程和內存的交互方式,在JMM抽象模型中,分為主內存、工作內存;主內存是所有線程共享的,一般是實例對象、靜態字段、數組對象等存儲在堆內存中的變量。工作內存是每個線程獨占的,線程對變量的所有操作都必須在工作內存中進行,不能直接讀寫主內存中的變量,線程之間的共享變量值的傳遞都是基于主內存來完成。
在JMM中,定義了8個原子操作來實現一個共享變量如何從主內存拷貝到工作內存,以及如何從工作內存同步到主內存,交互如下

8個原子操作指令
lock(鎖定):作用于主內存的變量,把一個變量標識為一條線程獨占狀態。
unlock(解鎖):作用于主內存變量,把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定。
read(讀取):作用于主內存變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨后的load動作使用
load(載入):作用于工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
use(使用):作用于工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作。
assign(賦值):作用于工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作。
store(存儲):作用于工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨后的write的操作。
write(寫入):作用于主內存的變量,它把store操作從工作內存中一個變量的值傳送到主內存的變量中。
順序一致性
如果要把一個變量從主內存中復制到工作內存,就需要按順尋地執行read和load操作,如果把變量從工作內存中同步回主內存中,就要按順序地執行store和write操作。JMM只要求這兩個操作必須按順序執行,而沒有保證必須是連續執行。也就是read和load之間,store和write之間是可以插入其他指令的,如對主內存中的變量a、b進行訪問時,可能的順序是read a,read b,load b, load a。

JMM不保證未同步程序的執行結果與該程序在順序一致性模型中的執行結果一致,因為如果想要保證執行結果一致,意味著JMM需要進制處理器和編譯器的優化,這對于程序的執行性能會產生很大的影響。所以在未同步程序的執行中,由于執行順序的不確定性導致結果無法預測。我們可以使用同步原語比如 synchronized,volatile、final來實現程序的同步操作來保證順序一致性

假如有兩個線程A和B并行執行,A和B線程分別都有3個操作,在程序中的順序是 A1->A2->A3, B1->B2->B3。
假設這兩個程序沒有使用同步原語,那么線程并行執行的效果可能是

如果這兩個程序使用了監視器鎖來實現正確同步,那么執行的過程一定是

重排序

CPU層面的內存亂序訪問屬于重排序的一部分,同時我們還提到了編譯器的優化執行的重排序。重排序是一種優化手段,但是在多線程并發中,會導致可見性問題。
編譯器的重排序是指,在不改變單線程程序語義的前提下,可以重新安排語句的執行順序來優化程序的性能.
編譯器的重排序和CPU的重排序的原則一樣,會遵守數據依賴性原則,編譯器和處理器不會改變存在數據依賴關系的兩個操作的執行順序,比如下面的代碼,這三種情況在單線程里面如果改變代碼的執行順序,都會導致結果不一致,所以重排序不會對這類的指令做優化,也就是需要滿足 as-if-serial語義

//寫后讀
a=1;
b=1;
//寫后寫
a=1;
a=2;
//讀后寫
a=b;
b=1;
as-if-serial語義
as-if-serial語義的意思是不管怎么重排序,單線程程序的執行結果不能被改變,編譯器、處理器都必須遵守這個語義

JMM層面的內存屏障

為了保證內存可見性,Java編譯器在生成指令序列的適當位置會插入內存屏障來禁止特定類型的處理器的重排序,在JMM中把內存屏障分為四類

屏障的作用這里就不重復再說了,實際上JMM層面的內存屏障就是對CPU層面的內存屏障指令做的包裝,作用是通過在合適的位置插入內存屏障來保證可見性

JVM是如何在JMM層面解決原子性、有序性、可見性問題的呢?

相信通過上面的分析,基本上有了答案

原子性:Java中提供了兩個高級指令 monitorenter和 monitorexit,也就是對應的synchronized同步鎖來保證原子性

可見性:volatile、synchronized、final都可以解決可見性問題

有序性:synchronized和volatile可以保證多線程之間操作的有序性,volatile會禁止指令重排序

volatile源碼分析

如果你看到這個章節了,意味著你對可見性有一個清晰的認識了,也知道JMM是基于禁止指令重排序來實現可見性的,那么我們再來分析volatile的源碼,就會簡單很多

基于最開始演示的這段代碼作為入口

public class VolatileDemo {
    public volatile static boolean stop=false;
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            int i=0;
            while(!stop){
                i++;
            }
        });
        thread.start();
        System.out.println("begin start thread");
        Thread.sleep(1000);
        stop=true;
    }
}

通過 javap-vVolatileDemo.class查看字節碼指令

public static volatile boolean stop;
    descriptor: Z
    flags: ACC_PUBLIC, ACC_STATIC, ACC_VOLATILE
...//省略
 public static void main(java.lang.String[]) throws java.lang.InterruptedException;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #2                  // class java/lang/Thread
         3: dup
         4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
         9: invokespecial #4                  // Method java/lang/Thread."":(Ljava/lang/Runnable;)V
        12: astore_1
        13: aload_1
        14: invokevirtual #5                  // Method java/lang/Thread.start:()V
        17: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        20: ldc           #7                  // String begin start thread
        22: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: ldc2_w        #9                  // long 1000l
        28: invokestatic  #11                 // Method java/lang/Thread.sleep:(J)V
        31: iconst_1
        32: putstatic     #12                 // Field stop:Z
        35: return

注意被修飾了volatile關鍵字的 stop字段,會多一個 ACC_VOLATILE的flag,在給 stop復制的時候,調用的字節碼是 putstatic,這個字節碼會通過BytecodeInterpreter解釋器來執行,找到Hotspot的源碼 bytecodeInterpreter.cpp文件,搜索 putstatic指令定位到代碼

CASE(_putstatic):
        {
          u2 index = Bytes::get_native_u2(pc+1);
          ConstantPoolCacheEntry* cache = cp->entry_at(index);
          if (!cache->is_resolved((Bytecodes::Code)opcode)) {
            CALL_VM(InterpreterRuntime::resolve_get_put(THREAD, (Bytecodes::Code)opcode),
                    handle_exception);
            cache = cp->entry_at(index);
          }

#ifdef VM_JVMTI
          if (_jvmti_interp_events) {
            int *count_addr;
            oop obj;
            // Check to see if a field modification watch has been set
            // before we take the time to call into the VM.
            count_addr = (int *)JvmtiExport::get_field_modification_count_addr();
            if ( *count_addr > 0 ) {
              if ((Bytecodes::Code)opcode == Bytecodes::_putstatic) {
                obj = (oop)NULL;
              }
              else {
                if (cache->is_long() || cache->is_double()) {
                  obj = (oop) STACK_OBJECT(-3);
                } else {
                  obj = (oop) STACK_OBJECT(-2);
                }
                VERIFY_OOP(obj);
              }

              CALL_VM(InterpreterRuntime::post_field_modification(THREAD,
                                          obj,
                                          cache,
                                          (jvalue *)STACK_SLOT(-1)),
                                          handle_exception);
            }
          }
#endif /* VM_JVMTI */

          // QQQ Need to make this as inlined as possible. Probably need to split all the bytecode cases
          // out so c++ compiler has a chance for constant prop to fold everything possible away.

          oop obj;
          int count;
          TosState tos_type = cache->flag_state();

          count = -1;
          if (tos_type == ltos || tos_type == dtos) {
            --count;
          }
          if ((Bytecodes::Code)opcode == Bytecodes::_putstatic) {
            Klass* k = cache->f1_as_klass();
            obj = k->java_mirror();
          } else {
            --count;
            obj = (oop) STACK_OBJECT(count);
            CHECK_NULL(obj);
          }

          //
          // Now store the result
          //
          int field_offset = cache->f2_as_index();
          if (cache->is_volatile()) {
            if (tos_type == itos) {
              obj->release_int_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == atos) {
              VERIFY_OOP(STACK_OBJECT(-1));
              obj->release_obj_field_put(field_offset, STACK_OBJECT(-1));
              OrderAccess::release_store(&BYTE_MAP_BASE[(uintptr_t)obj >> CardTableModRefBS::card_shift], 0);
            } else if (tos_type == btos) {
              obj->release_byte_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == ltos) {
              obj->release_long_field_put(field_offset, STACK_LONG(-1));
            } else if (tos_type == ctos) {
              obj->release_char_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == stos) {
              obj->release_short_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == ftos) {
              obj->release_float_field_put(field_offset, STACK_FLOAT(-1));
            } else {
              obj->release_double_field_put(field_offset, STACK_DOUBLE(-1));
            }
            OrderAccess::storeload();
          } else {
            if (tos_type == itos) {
              obj->int_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == atos) {
              VERIFY_OOP(STACK_OBJECT(-1));
              obj->obj_field_put(field_offset, STACK_OBJECT(-1));
              OrderAccess::release_store(&BYTE_MAP_BASE[(uintptr_t)obj >> CardTableModRefBS::card_shift], 0);
            } else if (tos_type == btos) {
              obj->byte_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == ltos) {
              obj->long_field_put(field_offset, STACK_LONG(-1));
            } else if (tos_type == ctos) {
              obj->char_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == stos) {
              obj->short_field_put(field_offset, STACK_INT(-1));
            } else if (tos_type == ftos) {
              obj->float_field_put(field_offset, STACK_FLOAT(-1));
            } else {
              obj->double_field_put(field_offset, STACK_DOUBLE(-1));
            }
          }
...//省略很多代碼

其他代碼不用管,直接看 cache->is_volatile()這段代碼,cache是 stop在常量池緩存中的一個實例,這段代碼是判斷這個cache是否是被 volatile修飾, is_volatile()方法的定義在 accessFlags.hpp文件中,代碼如下

public:
  // Java access flags
  ...//
  bool is_volatile    () const         { return (_flags & JVM_ACC_VOLATILE    ) != 0; }
  bool is_transient   () const         { return (_flags & JVM_ACC_TRANSIENT   ) != 0; }
  bool is_native      () const         { return (_flags & JVM_ACC_NATIVE      ) != 0; }

is_volatile是判斷是否有 ACC_VOLATILE這個flag,很顯然,通過 volatile修飾的stop的字節碼中是存在這個flag的,所以 is_volatile()返回true
接著,根據當前字段的類型來給 stop賦值,執行 release_byte_field_put方法賦值,這個方法的實現在 oop.inline.hpp中

inline void oopDesc::release_byte_field_put(int offset, jbyte contents)     
{ OrderAccess::release_store(byte_field_addr(offset), contents); }

賦值的動作被包裝了一層,看看 OrderAccess::release_store做了什么事情呢?這個方法的定義在 orderAccess.hpp中,具體的實現,根據不同的操作系統和CPU架構,調用不同的實現

以 orderAccess_linux_x86.inline.hpp為例,找到 OrderAccess::release_store的實現,代碼如下

inline void     OrderAccess::release_store(volatile jbyte*   p, jbyte   v) { *p = v; }

可以看到其實Java的volatile操作,在JVM實現層面第一步是給予了C++的原語實現。c/c++中的volatile關鍵字,用來修飾變量,通常用于語言級別的 memory barrier。被volatile聲明的變量表示隨時可能發生變化,每次使用時,都必須從變量i對應的內存地址讀取,編譯器對操作該變量的代碼不再進行優化

賦值操作完成以后,如果大家仔細看了前面putstatic的代碼,就會發現還會執行一個 OrderAccess::storeload();的代碼,這個代碼的實現是在 orderAccess_linux_x86.inline.hpp,它其實就是一個storeload內存屏障,JVM層面的四種內存屏障的定義以及實現
inline void OrderAccess::loadload()   { acquire(); }
inline void OrderAccess::storestore() { release(); }
inline void OrderAccess::loadstore()  { acquire(); }
inline void OrderAccess::storeload()  { fence(); }

當調用 storeload屏障時,它會調用fence()方法

inline void OrderAccess::fence() {
  if (os::is_MP()) { //返回是否多處理器,如果是多處理器才有必要增加內存屏障
    // always use locked addl since mfence is sometimes expensive
#ifdef AMD64
    //__asm__ volatile 嵌入匯編指令
    //lock 匯編指令,lock指令會鎖住操作的緩存行,也就是緩存鎖的實現
    __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
    __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
  }
}

os::is_MP()判斷是否是多核,如果是單核,那么就不存在內存不可見或者亂序的問題 __volatile__:禁止編譯器對代碼進行某些優化.
Lock :匯編指令,lock指令會鎖住操作的緩存行(cacheline), 一般用于read-Modify-write的操作;用來保證后續的操作是原子的
cc代表的是寄存器,memory代表是內存;這邊同時用了”cc”和”memory”,來通知編譯器內存或者寄存器內的內容已經發生了修改,要重新生成加載指令(不可以從緩存寄存器中取)
這邊的read/write請求不能越過lock指令進行重排,那么所有帶有lock prefix指令(lock ,xchgl等)都會構成一個天然的x86 Mfence(讀寫屏障),這里用lock指令作為內存屏障,然后利用asm volatile("" ::: "cc,memory")作為編譯器屏障. 這里并沒有使用x86的內存屏障指令(mfence,lfence,sfence),應該是跟x86的架構有關系,x86處理器是強一致內存模型

storeload屏障是固定調用的方法?為什么要固定調用呢?

原因是:避免volatile寫與后面可能有的volatile讀/寫操作重排序。因為編譯器常常無法準確判斷在一個volatile寫的后面是否需要插入一個StoreLoad屏障。為了保證能正確實現volatile的內存語義,JMM在采取了保守策略:在每個volatile寫的后面,或者在每個volatile讀的前面插入一個StoreLoad屏障。因為volatile寫-讀內存語義的常見使用模式是:一個寫線程寫volatile變量,多個讀線程讀同一個volatile變量。當讀線程的數量大大超過寫線程時,選擇在volatile寫之后插入StoreLoad屏障將帶來可觀的執行效率的提升。從這里可以看到JMM在實現上的一個特點:首先確保正確性,然后再去追求執行效率

總結

綜上分析可以得知,volatile是通過防止指令重排序來實現多線程對于共享內存的可見性。內容涉及比較多,有問題可以微信留言

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72572.html

相關文章

  • 金三銀四,2019大廠Android高級工程師面試題整理

    摘要:原文地址游客前言金三銀四,很多同學心里大概都準備著年后找工作或者跳槽。最近有很多同學都在交流群里求大廠面試題。 最近整理了一波面試題,包括安卓JAVA方面的,目前大廠還是以安卓源碼,算法,以及數據結構為主,有一些中小型公司也會問到混合開發的知識,至于我為什么傾向于混合開發,我的一句話就是走上編程之路,將來你要學不僅僅是這些,豐富自己方能與世接軌,做好全棧的裝備。 原文地址:游客kutd...

    tracymac7 評論0 收藏0
  • 手撕面試官系列():面試必備之常問并發編程高級面試專題

    摘要:如何在線程池中提交線程內存模型相關問題什么是的內存模型,中各個線程是怎么彼此看到對方的變量的請談談有什么特點,為什么它能保證變量對所有線程的可見性既然能夠保證線程間的變量可見性,是不是就意味著基于變量的運算就是并發安全的請對比下對比的異同。 并發編程高級面試面試題 showImg(https://upload-images.jianshu.io/upload_images/133416...

    Charles 評論0 收藏0
  • 阿里之路+Java面經考點

    摘要:我的是忙碌的一年,從年初備戰實習春招,年三十都在死磕源碼,三月份經歷了阿里五次面試,四月順利收到實習。因為我心理很清楚,我的目標是阿里。所以在收到阿里之后的那晚,我重新規劃了接下來的學習計劃,將我的短期目標更新成拿下阿里轉正。 我的2017是忙碌的一年,從年初備戰實習春招,年三十都在死磕JDK源碼,三月份經歷了阿里五次面試,四月順利收到實習offer。然后五月懷著忐忑的心情開始了螞蟻金...

    姘擱『 評論0 收藏0

發表評論

0條評論

marek

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<