线程与println()语句的关系

8

我试图创建一些场景,以展示在跨线程共享变量时的可见性问题。我注意到,在我测试的几乎所有情况下,如果我在run()内部添加一个System.out.println()语句,并且在使用共享变量的代码块中,可见性问题就不会出现。我提供一个例子:

配置细节 - Oracle Java6 64位,Eclipse Juno SR 2

  • 1)WITH VISIBILITY ISSUE:

    public class NoVisibility_Demonstration extends Thread {
    boolean keepRunning = true;
    public static void main(String[] args) throws InterruptedException {
        NoVisibility_Demonstration t = new NoVisibility_Demonstration();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println("keepRunning is false");
    }
    public void run() {
        int x = 1;
        while (keepRunning) 
        {
            //System.out.println("If you uncomment this line, the code will work without the visibility issue");
            x++;
    
        }
        System.out.println("x:"+x);
    }
    

    }

输出: 线程无限运行。

  • 2) 无可见性问题:

与上面相同的代码,只不过在 run() 中取消了注释的 println() 语句

输出:

...

如果您取消这行代码的注释,那么代码将可以正常工作而没有可见性问题。

如果您取消这行代码的注释,那么代码将可以正常工作而没有可见性问题。

如果您取消这行代码的注释,那么代码将可以正常工作而没有可见性问题。

x:19391

keepRunning 为 false

由于我在尝试的所有示例中都发现了类似的行为,因此我想知道 JVM 是否在任何 I/O 操作之前进行数据完整性检查。


3
在那个while循环中使用System.out.println()会显著影响每次循环迭代完成所需的时间。向控制台输出信息是慢的,而简单地增加一个int变量非常快速。这只是需要记住的一些事情。 - JonK
@JonK 我注意到我写的另一个程序也有这个问题。是的,差别很大。 - Biman Tripathy
2个回答

10

PrintWriter是同步的。

public void println(String x) {
    synchronized(this) {
        this.print(x);
        this.newLine();
    }
}

在主线程和第二个线程中连续调用 System.out.println() 方法可以创建两个线程之间的同步顺序。这意味着,在释放监视器(退出同步方法)之前在主线程中发生的所有操作(在这种情况下,是变量更新),将被在第二个线程获取监视器(进入同步方法)后执行的代码所看到。

简单来说,是的,调用 System.out.println() 会产生这种同步效果。


谢谢。现在清晰多了。 虽然我看到无论我是否在主线程中注释println()语句,都不会影响这个观察结果。只有run()中的println()会导致差异。 此外,即使我在主线程中启动其他线程之前放置一个println()语句,也没有影响。 - Biman Tripathy
@BimanTripathy 我不知道你为什么会得到这些结果。它们稳定吗? - AdamSkywalker
是的,这种行为每次都会出现。你的意思是你得到的结果和我得到的结果不一样吗? - Biman Tripathy
好的,明白了。在这种情况下,主线程是否知道共享变量的正确状态并不重要。恍然大悟!谢谢。 - Biman Tripathy

2

这种行为是特定于实现的。在OpenJDK中,println的主体虽然API没有声明同步,但实际上是同步的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接