前面写了一篇文章,讨论了i++和++i的区别(https://blog.csdn.net/lijianqingfeng/article/details/103474225)。该文的前提都是不存在并发的情况,通过i++ 和++i编译后的代码分析其执行效果。
public class Test {
private static volatile int count = 0;
public static void increment() {
count++;
}
public static void main(String[] args) throws InterruptedException {
IntStream.range(0, 10)
.forEach(i ->
new Thread(() -> IntStream.range(0, 1000).forEach(j -> increment())).start());
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(count);
}
}
程序运行结果并不是10000, 而是一个不确定的数,说明在i++并不是原子操作(即便是已经加上了volatile)。
为什么呢?看一下编译后的increment方法代码。
public static void increment();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field count:I
3: iconst_1
4: iadd
5: putstatic #2 // Field count:I
8: return
LineNumberTable:
line 11: 0
line 12: 8
突然发现,这里不是用的iinc了。count是static的,在内存中应该是放在方法区内。所以每次使用和保存都需要用getstatic和putstatic。
那么,修改成类变量呢?
public class Test {
private volatile int count = 0;
public void increment() {
count++;
}
public static void main(String[] args) {
Test test = new Test();
IntStream.range(0, 10)
.forEach(i ->
new Thread(() -> IntStream.range(0, 1000).forEach(j -> test.increment())).start());
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(test.count);
}
}
public void increment();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #2 // Field count:I
5: iconst_1
6: iadd
7: putfield #2 // Field count:I
10: return
LineNumberTable:
line 11: 0
line 12: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/baidu/adu/test/Test;
发现,这里也不是iinc,用的是getfield,从编译结果的命令上已经不是原子性的了,所以整个++操作肯定不是原子的。
至于iinc是否原子操作也无从分析了。。。