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

溫馨提示×

溫馨提示×

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

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

volatile怎么實現的內存可見

發布時間:2021-10-26 10:37:12 來源:億速云 閱讀:161 作者:iii 欄目:編程語言

本篇內容主要講解“volatile怎么實現的內存可見”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“volatile怎么實現的內存可見”吧!

volatile 講解

1. 可見性案例

public class ApiTest {

    public static void main(String[] args) {
        final VT vt = new VT();

        Thread Thread01 = new Thread(vt);
        Thread Thread02 = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException ignore) {
                }
                vt.sign = true;
                System.out.println("vt.sign = true 通知 while (!sign) 結束!");
            }
        });

        Thread01.start();
        Thread02.start();
    }

}

class VT implements Runnable {

    public boolean sign = false;

    public void run() {
        while (!sign) {
        }
        System.out.println("你壞");
    }
}
 

「這段代碼」,是兩個線程操作一個變量,程序期望當 sign 在線程 Thread01 被操作 vt.sign = true 時,Thread02 輸出 你壞。

但實際上這段代碼永遠不會輸出 你壞,而是一直處于死循環。這是為什么呢?接下來我們就一步步講解和驗證。 

2. 加上volatile關鍵字

我們把 sign 關鍵字加上 volatitle 描述,如下:

class VT implements Runnable {

    public volatile boolean sign = false;

    public void run() {
        while (!sign) {
        }
        System.out.println("你壞");
    }
}
 

「測試結果」

vt.sign = true 通知 while (!sign) 結束!
你壞

Process finished with exit code 0
 

volatile關鍵字是Java虛擬機提供的的最輕量級的同步機制,它作為一個修飾符出現,用來修飾變量,但是這里不包括局部變量哦

在添加 volatile 關鍵字后,程序就符合預期的輸出了 你壞。從我們對 volatile 的學習認知可以知道。volatile關鍵字是 JVM 提供的最輕量級的同步機制,用來修飾變量,用來保證變量對所有線程可見性。

正在修飾后可以讓字段在線程見可見,那么這個屬性被修改值后,可以及時的在另外的線程中做出相應的反應。 

3. volatile怎么保證的可見性

 
3.1 無volatile時,內存變化
volatile怎么實現的內存可見
無volatile時,內存變化

首先是當 sign 沒有 volatitle 修飾時 public boolean sign = false;,線程01對變量進行操作,線程02并不會拿到變化的值。所以程序也就不會輸出結果 “你壞” 

3.2 有volatile時,內存變化
volatile怎么實現的內存可見
有volatile時,內存變化

當我們把變量使用 volatile 修飾時 public volatile boolean sign = false;,線程01對變量進行操作時,會把變量變化的值強制刷新的到主內存。當線程02獲取值時,會把自己的內存里的 sign 值過期掉,之后從主內存中讀取。所以添加關鍵字后程序如預期輸出結果。

4. 反編譯解毒可見性

類似這樣有深度的技術知識,最佳的方式就是深入理解原理,看看它到底做了什么才保證的內存可見性操作。

 4.1 查看JVM指令

「指令」javap -v -p VT

 public volatile boolean sign;
    descriptor: Z
    flags: ACC_PUBLIC, ACC_VOLATILE

  org.itstack.interview.test.VT();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_0
         6: putfield      #2                  // Field sign:Z
         9: return
      LineNumberTable:
        line 35: 0
        line 37: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lorg/itstack/interview/test/VT;

  public void run();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field sign:Z
         4: ifne          10
         7: goto          0
        10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: ldc           #4                  // String 你壞
        15: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        18: return
      LineNumberTable:
        line 40: 0
        line 42: 10
        line 43: 18
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      19     0  this   Lorg/itstack/interview/test/VT;
      StackMapTable: number_of_entries = 2
        frame_type = 0 /* same */
        frame_type = 9 /* same */
}
 

從JVM指令碼中只會發現多了,ACC_VOLATILE,并沒有什么其他的點。所以,也不能看出是怎么實現的可見性。

 
4.2 查看匯編指令

通過Class文件查看匯編,需要下載 hsdis-amd64.dll 文件,復制到 JAVA_HOME\jre\bin\server目錄下。下載資源如下:

  • http://vorboss.dl.sourceforge.net/project/fcml/fcml-1.1.1/hsdis-1.1.1-win32-amd64.zip
  • http://vorboss.dl.sourceforge.net/project/fcml/fcml-1.1.1/hsdis-1.1.1-win32-i386.zip

另外是執行命令,包括:

  1. 基礎指令:      java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
  2. 指定打印:      -XX:CompileCommand=dontinline,類名.方法名
  3. 指定打印:      -XX:CompileCommand=compileonly,類名.方法名
  4. 輸出位置:      > xxx

最終使用:java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=dontinline,ApiTest.main -XX:CompileCommand=compileonly,ApiTest.mian

指令可以在IDEA中的 Terminal 里使用,也可以到 DOS黑窗口中使用

「另外」,為了更簡單的使用,我們把指令可以配置到idea的 VM options 里,如下圖:

volatile怎么實現的內存可見
Idea VM options 配置編譯指令

配置完成后,不出意外的運行結果如下:

Loaded disassembler from C:\Program Files\Java\jdk1.8.0_161\jre\bin\server\hsdis-amd64.dll
Decoding compiled method 0x0000000003744990:
Code:
Argument 0 is unknown.RIP: 0x3744ae0 Code size: 0x00000110
[Disassembling for mach='amd64']
[Entry Point]
[Constants]
  # {method} {0x000000001c853d18} 'getSnapshotTransformerList' '()[Lsun/instrument/TransformerManager$TransformerInfo;' in 'sun/instrument/TransformerManager'
  #           [sp+0x40]  (sp of caller)
  0x0000000003744ae0: mov     r10d,dword ptr [rdx+8h]
  0x0000000003744ae4: shl     r10,3h
  0x0000000003744ae8: cmp     r10,rax
  0x0000000003744aeb: jne     3685f60h          ;   {runtime_call}
  0x0000000003744af1: nop     word ptr [rax+rax+0h]
  0x0000000003744afc: nop
[Verified Entry Point]
  0x0000000003744b00: mov     dword ptr [rsp+0ffffffffffffa000h],eax
  0x0000000003744b07: push    rbp
  0x0000000003744b08: sub     rsp,30h           ;*aload_0
                                                ; - sun.instrument.TransformerManager::getSnapshotTransformerList@0 (line 166)

  0x0000000003744b0c: mov     eax,dword ptr [rdx+10h]
  0x0000000003744b0f: shl     rax,3h            ;*getfield mTransformerList
                                                ; - sun.instrument.TransformerManager::getSnapshotTransformerList@1 (line 166)

  0x0000000003744b13: add     rsp,30h
...
 

「運行結果就是匯編指令」,比較多這里就不都放了。我們只觀察????重點部分:

   0x0000000003324cda: mov    0x74(%r8),%edx     ;*getstatic state
                                                 ; - VT::run@28 (line 27)
 
   0x0000000003324cde: inc    %edx
   0x0000000003324ce0: mov    %edx,0x74(%r8)
   0x0000000003324ce4: lock addl $0x0,(%rsp)     ;*putstatic state
                                                 ; - VT::run@33 (line 27)
 

編譯后的匯編指令中,有volatile關鍵字和沒有volatile關鍵字,主要差別在于多了一個 lock addl $0x0,(%rsp),也就是lock的前綴指令。

「lock指令」相當于一個內存屏障,它保證如下三點:

  1. 將本處理器的緩存寫入內存。
  2. 重排序時不能把后面的指令重排序到內存屏障之前的位置。
  3. 如果是寫入動作會導致其他處理器中對應的內存無效。

那么,這里的1、3就是用來保證被修飾的變量,保證內存可見性。

 

5. 不加volatile也可見嗎

有質疑就要有驗證

我們現在再把例子修改下,在 while (!sign) 循環體中添加一段執行代碼,如下;

class VT implements Runnable {

    public boolean sign = false;

    public void run() {
        while (!sign) {
            System.out.println("你好");
        }
        System.out.println("你壞");
    }
    
}
 

修改后去掉了 volatile 關鍵字,并在while循環中添加一段代碼。現在的運行結果是:

...
你好
你好
你好
vt.sign = true 通知 while (!sign) 結束!
你壞

Process finished with exit code 0

到此,相信大家對“volatile怎么實現的內存可見”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

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

AI

哈尔滨市| 江川县| 资溪县| 邵阳市| 连江县| 四子王旗| 玉屏| 内乡县| 承德县| 云安县| 长春市| 京山县| 霍林郭勒市| 乡城县| 临城县| 闸北区| 鲁山县| 都兰县| 酒泉市| 江孜县| 金昌市| 五原县| 宁武县| 汪清县| 元氏县| 四子王旗| 泊头市| 汉中市| 班玛县| 华池县| 安乡县| 淮南市| 正蓝旗| 腾冲县| 蛟河市| 兴宁市| 新巴尔虎左旗| 大洼县| 西青区| 龙南县| 收藏|