volitile关键字用于解决多线程并发执行时可能出现的可见性问题
缓存锁定
处理器在对已经加载到cache当中的共享数据进行修改时,会向总线发出命令,其他处理器通过嗅探总线发现有处理器请求写内存地址,而该内存地址为共享地址,处理器就会使存有该共享内存地址的cache行失效。处理器修改完共享数据后,会将结果写回内存。这样,其他处理器下次对共享数据的操作就会先从主存当中读取该数据到cache中,然后再进行处理,这样得到的就是最新的数据,避免了可见性问题。
根据书中所写,对加了volatile关键字的变量进行读写操作时,生成的汇编代码前会加上lock。

于是,我写了这样一段demo
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class test { static volatile int temp = 10; public static void main(String[] args) { temp++; Goods bottle = new Goods(); bottle.price++; } }
class Goods{ volatile int price = 0; Goods(){} }
|
然后通过javac以及javap命令在终端查看它编译后的汇编代码
1 2 3
| javac -g:none -d out src/test.java javap -c -v -p out/test.class > out/test.asm
|
得到的汇编文件中关键代码部分如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| { static volatile int temp; descriptor: I flags: (0x0048) ACC_STATIC, ACC_VOLATILE
public test(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: getstatic #7 // Field temp:I 3: iconst_1 4: iadd 5: putstatic #7 // Field temp:I 8: new #13 // class Goods 11: dup 12: invokespecial #15 // Method Goods."<init>":()V 15: astore_1 16: aload_1 17: dup 18: getfield #16 // Field Goods.price:I 21: iconst_1 22: iadd 23: putfield #16 // Field Goods.price:I 26: return
static {}; descriptor: ()V flags: (0x0008) ACC_STATIC Code: stack=1, locals=0, args_size=0 0: bipush 10 2: putstatic #7 // Field temp:I 5: return }
|
没有找到带有lock前缀的汇编指令,于是我去求助了一下gpt,得到的解答如下
当你查看 Java 代码编译后的汇编代码时,如果发现加了 volatile 的变量对应的汇编语句没有出现 lock 前缀,可能有以下几个原因:
- 编译器优化:编译器在生成汇编代码时可能会进行优化,将 volatile 变量的读写操作优化为更高效的方式,而不是简单地添加 lock 前缀。
- 硬件层面支持:某些处理器对 volatile 变量的操作可能有硬件层面的支持,因此在汇编代码中不需要显式添加 lock。
- 汇编代码层面隐藏:lock 前缀可能被隐藏在汇编代码的更底层的指令中,不一定会直接出现在你查看的汇编代码中。