使用System.out.println(Thread.currentThread().getName() + " " +count);会导致同步问题。

4
我正在学习Java中的多线程。在教程中,它说去掉synchronized会使程序出现bug,实际上也确实如此。所以我进行了一些实验,写了一行代码:System.out.println(Thread.currentThread().getName() + " " +count);并且去掉了synchronized关键字。即使这样,程序也能正常运行。但是如果仅仅去掉了synchronized关键字而没有加上printline(System.out.println(Thread.currentThread().getName() + " " +count);),那么程序会出现预期的bug。
我无法理解为什么添加一行打印代码就可以使其同步。
public class intro implements Runnable {

    int n=10000; 
    private int count = 0; 

    public int getCount() { return count; }

    public synchronized void incrSync() { count++;  }

    public void run () {
        for (int i=0; i<n; i++) {
            incrSync();
            //System.out.println(Thread.currentThread().getName() + "  " +count);       
        } 
        } 


    public static void main(String [] args) {

        intro mtc = new intro();
        Thread t1 = new Thread(mtc);
        Thread t2 = new Thread(mtc);
        t1.start();
        t2.start();
        try {
            t1.join();

            t2.join();

        } catch (InterruptedException ie) {
        System.out.println(ie);
        ie.printStackTrace();
    }
    System.out.println("count = "+ mtc.getCount());
}
}

3
println() 方法是同步的,因此会导致线程获取和释放锁。当然,您不应该依赖此特性来使您的代码线程安全。 - JB Nizet
javadoc没有提到一个方法(或其内部代码)是否同步。这是实现细节,不是API的一部分。但PrintStream的源代码随JDK一起提供。只需查看它即可。 - JB Nizet
我会检查一下,但这不应该同步 println 调用方法。 - 11thdimension
3
它本身并不能解决问题。但是它可以减少竞争状态的发生,使程序“看起来”更加安全(实际上并非如此)。 - JB Nizet
1
还要记住,在某些情况下(例如双重检查锁定),仅有同步方法是不够的;您还必须使变量“volatile”。(请参见https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java,了解详细信息的解释。) - Klitos Kyriacou
显示剩余3条评论
2个回答

3

当多个线程同时尝试访问同一字段时,线程之间会出现同步问题。

如果没有打印语句,则run方法将在一个紧密的循环中几乎持续地访问计数器。使用多个未同步的线程执行此操作极有可能导致故障。

通过添加打印语句,您将更改循环以使大部分(几乎全部)时间用于打印,并仅偶尔增加计数。这样就不太可能发生争用。

即使有打印语句,该代码仍然存在缺陷,唯一的区别是争用发生的频率要少得多,并且仅测试1000次循环是不足以证明问题的。您可能需要运行它几年才能使线程发生冲突。

这是为什么线程问题如此难以查找和修复的典型示例。该循环(带有其打印语句)可以在多个线程上运行多年而不产生争用,但如果线程之间发生了冲突,则代码将崩溃。想象一下这种情况发生在心脏起搏器、卫星或核电站中会怎样!


1
System.out.println 的某些部分本身是同步的事实也有所帮助。 - Mark Rotteveel
1
@MarkRotteveel - 的确如此,但只是为了进一步减少问题发生的可能性。它并不能消除问题。 - OldCurmudgeon
我知道,我只是评论一下,因为你的回答没有涉及到那个问题。 - Mark Rotteveel

0

println方法调用newline,它是一个带有synchronized-块的方法。虽然它不是线程安全的,但它仍会给出正确结果。

考虑T1和T2同时读取计数器并得到结果为5时,就会出现竞态条件。它之所以能够给出正确结果,是因为你使用了System.out.println(Thread.currentThread().getName() + " " +count);这个阻塞语句。增加线程数量。

 private void newLine() {
    try {
        synchronized (this) {
            ensureOpen();
            textOut.newLine();
            textOut.flushBuffer();
            charOut.flushBuffer();
            if (autoFlush)
                out.flush();
        }
    }
    catch (InterruptedIOException x) {
        Thread.currentThread().interrupt();
    }
    catch (IOException x) {
        trouble = true;
    }
}

1
虽然它是同步的,但我尝试使'n'变得更大(例如1000000),它仍然没有正确地求和(1999992)。 - herokingsley

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