System.out.println()对共享变量和多线程的影响,为什么会造成while循环终止

最近在了解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有时间去保证内存的可见性。

posted @ 2019-12-02 08:42  三分魔系  阅读(115)  评论(0编辑  收藏  举报