最近在了解volatile关键字,众所周知volatile可以保证共享变量的可见性,本文是记录在学习volatile过程中遇到的有趣事件。
首先看下面的代码:
private static boolean isStop = false;
public static void main(String[] args) {
new Thread() {
public void run() {
while (!isStop) {
}
};
}.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isStop = true;
}
由于isStop变量并未用volatile修饰,所以这个程序并不会在1s后退出,而是进入死循环,原因我就不再叙述了,百度一下volatile关键字就可以得到解释。你以为这就结束了吗,不,接下来,我在while中加入了一个System.out.println(“hello”);语句:
while (!isStop) {
System.out.println("hello");
}
这个时候奇怪的事发生了,程序在运行一段时间后退出了,这是为什么呢,第一时间当然是查看源码:
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
可以看到,println()方法是一个同步方法,锁条件是this,进入到System类中,在110行可以看到维护了一个final static的成员变量PrintStream out:
public final static PrintStream out = null;
初始化在1155行的initializeSystemClass()方法:
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn));
setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
由于System类中维护了一个final static的成员变量PrintStream out,所以系统中任何地方调用System.out.println()都是同步的,所以在生产环境中不推荐甚至是禁止使用的,可以使用日志来记录需要的信息。
但是这并不能解决我的疑惑,因为synchronized关键字同步,只能同步同步代码块里面的内容,很明显isStop变量是在同步代码块外面的,怎么会同步呢?
百度以后找到了原因,原因是这样的:JVM 会尽力保证内存的可见性,即便这个变量没有被同步关键字修饰。也就是说,只要 CPU 有时间,JVM 会尽力去保证变量值的更新。这种与 volatile 关键字的不同在于,volatile 关键字会强制的保证线程的可见性。而不加这个关键字,JVM 也会尽力去保证可见性,但是如果 CPU 一直有其他的事情在处理,就不能保证变量的更新。第一段代码使用了while死循环,占用了CPU的大量时间,第二段代码在while死循环中增加了System.out.println(),由于是同步的,在IO过程中,CPU空闲时间比较多就有可能有时间去保证内存的可见性。
下面的代码可能会更好的说明问题:
private static boolean isStop = false;
public static void main(String[] args) {
new Thread() {
public void run() {
while (!isStop) {
// System.out.println("hello");
try {
Thread.sleep(500);
} catch (
InterruptedException e) {
e.printStackTrace();
}
}
};
}.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isStop = true;
}
使用Thread.sleep(500);模拟System.out.println()同步输出时CPU的空闲时间,可以发现程序也可以正常退出,并不会一直死循环,这是由于此时CPU有时间去保证内存的可见性。