二、原子性
保证可见性可以保证一个线程写之后,另一个线程可以读到。 那假如一个进程既读取变量,又依赖读到的变量进行写操作呢?我们来看下面的例子 :
1、实例讲解
用两个线程分别执行10000次a++的操作,按道理来说,a的结果应该会增加20000
public class Test {
static int a = 0;
public static void main(String[] args) throws InterruptedException {
for(int i=0; i<10000; i++) {
new Thread(()-> {
a++;
}).start();
new Thread(()-> {
a++;
}).start();
}
Thread.sleep(1000); //为了保证不会影响,停一秒再写
System.out.println(a); //对a执行读操作
}
}
可以看到,结果和我们的预期对不上,那我们加上volatile关键字试一下:
结果还是对不上,这是为什么呢?
这就要探究a++的本质了
2、a++的本质
a++可以拆分为三个操作:1、读取a; 2、a+1; 3、将加之后的值赋给a
有可能会出现这种情况:
1、当a=0时,线程1读取a值,线程2也读取a值;
2、线程1将它读到的a值+1,此时为1,线程2也将它读到的a值+1,此时为1;
3、线程1将1这个值刷入主存,此时主存中的a=1;线程2也将1这个值刷入主存,此时为1
这显然是不对的,两个线程各执行一次a++,a的值应该+2才对。刚刚我们得到的值为19998,可能就是有两个线程在其他线程+之前读取到了a值。
那加上volatile关键字之后呢?
volatile关键字只能保证可见性,即一个线程写过之后,另一个线程能够立马读到。而假如线程2操作在线程1操作写之前就已经读了,那还是没办法改变这个情况。这两个线程都需要读取主存的值,并且每个线程都依赖自己读取的值进行写操作。这就需要保证原子性了。