volatile为什么不能保证原子性
不保证原子性的例子
public class Test {
public static volatile int data = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
Test.data++;
System.out.println(data);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
Test.data++;
System.out.println(data);
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终的data为:" + Test.data);
}
}
1
3
4
5
2
8
9
10
11
7
12
13
14
15
16
17
18
19
20
最终的data为:20
多线程同时工作去修改一个被volatile关键字修饰的共享变量的时候,比如两个线程对同一个变量volatile int i;进行i++操作,可能会导致运行结果跟预期不一致。这是因为volatile只能保证可见性和有序性,而不能保证原子性。
为什么不能保证原子性
首先看一下上面这张图,分析是这样子的:
1、每个线程都有自己的一块工作内存,将主存里的数据缓存到自己的工作内存中。
此时,Threa1跟Thread2都通过read+load将 data = 0 缓存到自己的工作内存中去了。
2、cpu切换到Thread1时,Thread1就会将data = 0 use 进cpu里面计算data++的操作,但是没来得及将计算结果data = 1, assign回Thread1的工作内存中。cpu此时切换到了Thread2,然后在Thread2线程也做了同样的操作(将data = 0 use进cpu并执行data++),继续assign回Thread2的工作内存,并store+write将data = 1写回主存。
因为data是用volatile修饰的,根据缓存一致性协议,其他的线程(这里指的是Thread1)就会立即知道自己工作内存中的数据(data = 0)已经失效了。然而,并没有用!因为Thread1之前已经将data = 0 use 进cpu进行计算去了,cpu切换回Thread1时,Thread1将之前计算好的data = 1 assign 到自己的工作内存中,并store+write写回主存。通过以上的执行顺序,两个线程分别对data做了一次data++操作之后,得到的data = 1 而不是我们预期中的data = 2。
3、总结:多线程同时修改一个被volatile修饰的共享变量时,不能保证原子性,只能保证可见性和顺序性。
如何保证原子性
当然是加锁啦!不要问,问就是用synchronized…只有加了锁,让别的线程连读的机会都没有,才能够安安心心的搞完 read-load-use-assign-store-write全套而不被中途打扰。
public class Test {
public static volatile int data = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronized (Test.class) {
Test.data++;
System.out.println(data);
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronized (Test.class) {
Test.data++;
System.out.println(data);
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终的data为:" + Test.data);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
最终的data为:20