来看一个例子:在这个例子中,我们定义了 increase() 方法,方法体是 num++ 的操作,我们通过编译成字节码文件中的jvm指令来看为什么 num++ 不是原子操作,而是包括读取变量的原始值、进行加1操作、写入工作内存。
1. 源文件 Test_volatile.java
public class Test_volatile {
private volatile int num = 0;
public void increase() {
num++;
}
public static void main(String[] args) throws InterruptedException {
final Test_volatile test = new Test_volatile();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++){
test.increase();
}
};
}.start();
}
// 睡眠 3s 保证 10个线程的自增操作全部执行完毕
Thread.sleep(3000);
System.out.println("num 的值为: " + test.num);
}
}
2. 编译后的Test_volatile.class文件
cmd查看编译后的文件的命令:javap -c Test_volatile
Compiled from "Test_volatile.java"
public class com.practice.concurrent.keyWord_volatile.Test_volatile {
public com.practice.concurrent.keyWord_volatile.Test_volatile();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field num:I
9: return
public void increase();
Code:
0: aload_0
1: dup
2: getfield #2 // Field num:I
5: iconst_1
6: iadd
7: putfield #2 // Field num:I
10: return
public static void main(java.lang.String[]) throws java.lang.InterruptedException;
Code:
0: new #3 // class com/practice/concurrent/keyWord_volatile/Test_volatile
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: iconst_0
9: istore_2
10: iload_2
11: bipush 10
13: if_icmpge 33
16: new #5 // class com/practice/concurrent/keyWord_volatile/Test_volatile$1
19: dup
20: aload_1
21: invokespecial #6 // Method com/practice/concurrent/keyWord_volatile/Test_volatile$1."<init>":(Lcom/practice/concurrent/keyWord_volatile/Test_volatile;)V
24: invokevirtual #7 // Method com/practice/concurrent/keyWord_volatile/Test_volatile$1.start:()V
27: iinc 2, 1
30: goto 10
33: ldc2_w #8 // long 3000l
36: invokestatic #10 // Method java/lang/Thread.sleep:(J)V
39: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream;
42: new #12 // class java/lang/StringBuilder
45: dup
46: invokespecial #13 // Method java/lang/StringBuilder."<init>":()V
49: ldc #14 // String num 的值为:
51: invokevirtual #15 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
54: aload_1
55: getfield #2 // Field num:I
58: invokevirtual #16 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
61: invokevirtual #17 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
64: invokevirtual #18 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
67: return
}
从编译后的结果来看, increase() 方法被编译为:
public void increase();
Code:
0: aload_0
1: dup
2: getfield #2 // Field num:I
5: iconst_1
6: iadd
7: putfield #2 // Field num:I
10: return
其中,这里有重要的几步,我加了注释进行说明:
2: getfield #2 // Field num:I // 这一步是从读取变量的原始值
5: iconst_1 // 这一步是 int型常量值1进栈,说白了就是把整数1准备好
6: iadd // 这一步是加1的操作
7: putfield #2 // Field num:I // 这一步是写入工作内存
感谢大家的阅读!到这里终于明白为什么 num++ 不是原子操作了,如果对Java文件编译之后的jvm指令感兴趣,大家可以去详细了解一下JVM指令集。