volatile关键字

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 前缀,可能有以下几个原因:

  1. 编译器优化:编译器在生成汇编代码时可能会进行优化,将 volatile 变量的读写操作优化为更高效的方式,而不是简单地添加 lock 前缀。
  2. 硬件层面支持:某些处理器对 volatile 变量的操作可能有硬件层面的支持,因此在汇编代码中不需要显式添加 lock。
  3. 汇编代码层面隐藏:lock 前缀可能被隐藏在汇编代码的更底层的指令中,不一定会直接出现在你查看的汇编代码中。