volatile-怎么实现的内存可见?没有-volatile-一定不可见吗?

如果一件小事能坚持5年以上,那你一定是很了不起的人。是的,很了不起。人最难的就是想清楚了但做不到,或者偶尔做到长期做不到。

其实大多数走在研发路上的伙伴们,都知道自己该努力,但明明下好了的决心就是坚持不了多久。直到有一天被瓶颈限制在困局中才会着急,但这时候在想破局就真的很难了!

二、面试题

谢飞机,小记,飞机趁着周末,吃完火锅。又去约面试官喝茶了!

谢飞机:嗨,我在这,这边,这边。

面试官:你怎么又来了,最近学的不错了?

谢飞机:还是想来大厂,别害羞,面我吧!

面试官:我好像是你补课老师… 既然来了,就问问你吧!volatile 是干啥的?

谢飞机:啊,volatile 是保证变量对所有线程的可见性的。

面试官:那 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时,内存变化

首先是当 sign 没有 volatitle 修饰时 public boolean sign = false;,线程01对变量进行操作,线程02并不会拿到变化的值。所以程序也就不会输出结果 “你坏”

3.2 有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.“”😦)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 配置编译指令

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

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

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!**

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值