您好,登錄后才能下訂單哦!
本篇內容主要講解“volatile怎么實現的內存可見”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“volatile怎么實現的內存可見”吧!
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 輸出 你壞。
但實際上這段代碼永遠不會輸出 你壞,而是一直處于死循環。這是為什么呢?接下來我們就一步步講解和驗證。
我們把 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 提供的最輕量級的同步機制,用來修飾變量,用來保證變量對所有線程可見性。
正在修飾后可以讓字段在線程見可見,那么這個屬性被修改值后,可以及時的在另外的線程中做出相應的反應。
首先是當 sign 沒有 volatitle 修飾時 public boolean sign = false;
,線程01對變量進行操作,線程02并不會拿到變化的值。所以程序也就不會輸出結果 “你壞”
當我們把變量使用 volatile 修飾時 public volatile boolean sign = false;
,線程01對變量進行操作時,會把變量變化的值強制刷新的到主內存。當線程02獲取值時,會把自己的內存里的 sign 值過期掉,之后從主內存中讀取。所以添加關鍵字后程序如預期輸出結果。
類似這樣有深度的技術知識,最佳的方式就是深入理解原理,看看它到底做了什么才保證的內存可見性操作。
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
,并沒有什么其他的點。所以,也不能看出是怎么實現的可見性。
通過Class文件查看匯編,需要下載 hsdis-amd64.dll 文件,復制到 JAVA_HOME\jre\bin\server目錄下
。下載資源如下:
另外是執行命令,包括:
java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
-XX:CompileCommand=dontinline,類名.方法名
-XX:CompileCommand=compileonly,類名.方法名
> xxx
最終使用:java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=dontinline,ApiTest.main -XX:CompileCommand=compileonly,ApiTest.mian
指令可以在IDEA中的 Terminal 里使用,也可以到 DOS黑窗口中使用
「另外」,為了更簡單的使用,我們把指令可以配置到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、3就是用來保證被修飾的變量,保證內存可見性。
有質疑就要有驗證
我們現在再把例子修改下,在 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怎么實現的內存可見”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。