}
}
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时,内存变化](https://upload-images.jianshu.io/upload_images/24613101-51f5a2ea06f73c6d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
首先是当 sign 没有 volatitle 修饰时 `public boolean sign = false;`,线程01对变量进行操作,线程02并不会拿到变化的值。所以程序也就不会输出结果 “你坏”
#### 3.2 有volatile时,内存变化
![有volatile时,内存变化](https://upload-images.jianshu.io/upload_images/24613101-af66801544a45884.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
当我们把变量使用 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.""😦)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 里,如下图:
![Idea VM options 配置编译指令](https://upload-images.jianshu.io/upload_images/24613101-5fa02d02d6727181.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
配置完成后,不出意外的运行结果如下:
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]
最后
给大家送一个小福利
资料附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。
**[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】](
)**
)]
资料附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。
**[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】](
)**
[外链图片转存中…(img-T6MiRFqo-1631187420776)]