volatile关键字的作用
volatile关键字是Java虚拟机提供的最轻量级的同步机制,volatile具有可见性和有序性,但是,不具有原子性特性。
Java中提供的操作运算符不具有原子性。
看下面例子:
public class Main {
public static volatile int race = 0;
private static final int THREAD_COUNT = 10;
public static void increase() {
race++;
}
public static void main(String[] args) {
final CountDownLatch downLatch = new CountDownLatch(THREAD_COUNT);
Runnable runnable = () -> {
for (int i = 0; i < 10000; i++) {
increase();
}
downLatch.countDown();
};
Thread[] threads = new Thread[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; i++) {
threads[i] = new Thread(runnable);
threads[i].start();
}
try {
downLatch.await();
} catch (InterruptedException exception) {
exception.printStackTrace();
}
System.out.println("end race = " + race);
}
}
结果:
期望:100000
输出:end race = 41573
输出结果不到100000,不是期望值,通过javap反编译:
Compiled from "Main.java"
public class example.Main {
public static volatile int race;
public example.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void increase();
Code:
0: getstatic #2 // Field race:I
3: iconst_1
4: iadd
5: putstatic #2 // Field race:I
8: return
public static void main(java.lang.String[]);
Code:
0: new #3 // class java/util/concurrent/CountDownLatch
3: dup
4: bipush 10
6: invokespecial #5 // Method java/util/concurrent/CountDownLatch."<init>":(I)V
9: astore_1
10: aload_1
11: invokedynamic #6, 0 // InvokeDynamic #0:run:(Ljava/util/concurrent/CountDownLatch;)Ljava/lang/Runnable;
16: astore_2
17: bipush 10
19: anewarray #7 // class java/lang/Thread
22: astore_3
23: iconst_0
24: istore 4
26: iload 4
28: bipush 10
30: if_icmpge 58
33: aload_3
34: iload 4
36: new #7 // class java/lang/Thread
39: dup
40: aload_2
41: invokespecial #8 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
44: aastore
45: aload_3
46: iload 4
48: aaload
49: invokevirtual #9 // Method java/lang/Thread.start:()V
52: iinc 4, 1
55: goto 26
58: aload_1
59: invokevirtual #10 // Method java/util/concurrent/CountDownLatch.await:()V
62: goto 72
65: astore 4
67: aload 4
69: invokevirtual #12 // Method java/lang/InterruptedException.printStackTrace:()V
72: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
75: new #14 // class java/lang/StringBuilder
78: dup
79: invokespecial #15 // Method java/lang/StringBuilder."<init>":()V
82: ldc #16 // String end race =
84: invokevirtual #17 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
87: getstatic #2 // Field race:I
90: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
93: invokevirtual #19 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
96: invokevirtual #20 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
99: return
Exception table:
from to target type
58 62 65 Class java/lang/InterruptedException
static {};
Code:
0: iconst_0
1: putstatic #2 // Field race:I
4: return
}
着重看increase()方法:
public static void increase();
Code:
0: getstatic #2 // Field race:I
3: iconst_1
4: iadd
5: putstatic #2 // Field race:I
8: return
在race变量读取和写入新值之间有“++”运算符操作,而Java运算符是不具有原子性的,导致race变量值出错。
这不代表volatile的同步机制有错,只是因为volatile不具有原子性,volatile关键字修饰的变量只能保证可见性和有序性,在不能保证原子性操作的场景,依然需要使用synchronized、java.util.concurrent中的锁和原子类来保证原子性。
volatile关键字在下面场景可以正常使用:
- 运算结果不依赖当前值,或者保证只有一条线程修改变量值。
- volatile修饰的变量不需要和其他状态变量参与不变约束。