zoukankan      html  css  js  c++  java
  • 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有时间去保证内存的可见性。

    一颗安安静静的小韭菜。文中如果有什么错误,欢迎指出。
  • 相关阅读:
    java学习笔记(二)分布式框架Dubbo+zookeeper搭建
    java学习笔记(一) 服务器的认识
    用slf4j+logback实现多功能日志解决方案 --- 转
    9.3.2 The force and release procedural statements
    3.7.4 Tri0 and tri1 nets
    9.3.1 The assign and deassign procedural statements
    10. Tasks and functions
    6.1.2 The continuous assignment statement
    df 查看磁盘使用情况
    信息学竞赛知识点整理
  • 原文地址:https://www.cnblogs.com/c-Ajing/p/13448376.html
Copyright © 2011-2022 走看看