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

34 阅读2分钟

一、可见性

1、实例讲解

先看这样一段代码:

public class Test {

    static boolean a = true;

    public static void main(String[] args) {
    	a = false;					//对a执行写操作
    	System.out.println(a); 		//对a执行读操作
    }

}

我们在单线程中,对a执行了写操作,并且读取到了最新写的值,也就是说,单线程中对a的写操作时可见的。

那么我们再开启一个线程 :

public class Test {

    static boolean a = true;

    public static void main(String[] args) throws InterruptedException {
    	new Thread(()-> {
    		while(a) {}	//死循环
    	}).start();
    	
    	Thread.sleep(1000);	//为了保证不会影响,停一秒再写		
    	a = false;					//对a执行写操作
    	System.out.println(a); 		//对a执行读操作
    }

}

可以看到,虽然a仍然打印出为false,但是程序没有结束,就说明在我们新开启的线程中a的值始终为true,他才可以一直执行while循环。换句话说,我们的主线程对a的写操作对于新开的线程的读操作来说是不可见的。

为什么这么长时间了,新开线程中的a还是true呢?那是因为新线程中一直在执行循环,使得线程没有机会去拿到主存中a的最新值,而是一直读取缓存中a的值。

那么,我们让循环沉睡一会儿,给他去读最新值的机会:

public class Test {

    static boolean a = true;

    public static void main(String[] args) throws InterruptedException {
    	new Thread(()-> {
    		while(a) {
    			try {
    				Thread.sleep(1);	//睡1ms,给线程去读新值的机会
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}	//死循环
    	}).start();
    	
    	Thread.sleep(1000);	//为了保证不会影响,停一秒再写		
    	a = false;					//对a执行写操作
    	System.out.println(a); 		//对a执行读操作
    }

}

可以看到,程序很快就结束了,说明新线程已经读到了a的新值为false,结束了循环。

2、如何理解Java线程中的不可见性?

简单来说:线程1读,线程2写,而线程1读不到线程2写的值,这就是不可见性。

3、那么如何实现可见性呢?

就需要用到volatile关键字了:

public class Test {

    static volatile boolean a = true;

    public static void main(String[] args) throws InterruptedException {
    	new Thread(()-> {
    		while(a) {}	//死循环
    	}).start();
    	
    	Thread.sleep(1000);	//为了保证不会影响,停一秒再写		
    	a = false;					//对a执行写操作
    	System.out.println(a); 		//对a执行读操作
    }

}