Java并发中的可见性和原子性(中篇)

76 阅读2分钟

二、原子性

保证可见性可以保证一个线程写之后,另一个线程可以读到。 那假如一个进程既读取变量,又依赖读到的变量进行写操作呢?我们来看下面的例子 :

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操作写之前就已经读了,那还是没办法改变这个情况。这两个线程都需要读取主存的值,并且每个线程都依赖自己读取的值进行写操作。这就需要保证原子性了。