一、前言
java内存模型中的可见性是指,当一个线程修改了共享变量的值后,其他线程可以立马知道这个修改后的值。在《Java并发编程:volatile关键字解析》中有这么一段话:
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
对于这一论点,网上鲜有论据。所以,笔者通过几个简单的例子来证明volatile确实可以保证可见性。
二、论证过程
每个线程都有一个工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递依靠主内存来完成。
以此原理,设计了如下的例子:
package ThreadPool;
public class VolatileTest {
public boolean isShutdown;
public boolean getShutdown () {
return isShutdown;
}
public void shutdown () {
isShutdown = true;
}
public class ReaderThread extends Thread {
@Override
public void run() {
try {
System.out.println("开始循环");
while (!isShutdown) {
};
System.out.println("结束循环");
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class WatchThread extends Thread {
@Override
public void run() {
shutdown();
}
}
public static void main(String[] args){
try {
VolatileTest volatileTest = new VolatileTest();
volatileTest.new ReaderThread().start();
//让主线程睡眠一秒,确保另一个线程调用shutdown方法时死循环已经开始
Thread.sleep(1000);
volatileTest.new WatchThread().start();
//此刻的睡眠是为了确保shutdown方法对isShutdown变量的修改已经同步到主内存
Thread.sleep(1000);
//打印isShutdown的值
System.out.println("getShutdown:" + volatileTest.getShutdown());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
isShutdown是共享变量,默认值是false。ReaderThread线程的执行体是一个死循环,直到isShutdown变为true才跳出循环,打印“结束循环”。WatchThread线程的执行体调用了shutdown方法,将isShutdown的值改为true。如果说ReaderThread线程跳出了死循环,那么就说明WatchThread线程对isShutdown的修改是对其可见的,反之,则不可见。
运行结果如下:
由此可见,WatchThread对isShutdown变量的修改是生效的,但ReaderThread并没有跳出循环,也就意味着ReaderThread并没有读取到isShutdown变量的最新值,而是读取工作内存中的旧值。所以,普通变量是不具有可见性的。
紧接着,我们用volatile去修饰isShutdown变量。
运行结果如下:
这一次,ReaderThread跳出了死循环,打印出了“结束循环”,这说明ReaderThread可以读取到isShutdown变量的最新值,换句话说就是,WatchThread对isShutdown变量的修改对于ReaderThread是可见的。
三、总结
综上所述,volatile确实可以保证可见性。