1

我在许多帖子中读过,认为这使得代码在某种程度上是线程安全的,因此应该从代码中删除System.out.println()模拟比赛。System.out.println()

现在同步write()方法在写入流之前进行,所以每次调用都会持有并释放一个锁。PrintStreamthiswrite()

write()PrintStream 方法

public void write(int b) {
    try {
        synchronized (this) { //acquires a lock
        ensureOpen();
        out.write(b);
        if ((b == '\n') && autoFlush)
            out.flush();
        }
    }
    catch (InterruptedIOException x) {
        Thread.currentThread().interrupt();
    }
    catch (IOException x) {
        trouble = true;
    }
    }

但这会影响种族的行为吗?

认为:

boolean flag = true;

Thread 1: 
System.out.println();
flag = false;
System.out.println();


Thread 2:
System.out.println();
while(flag){
    System.out.println();
}

现在,如果我们看到两个线程都在锁定同一个对象,即this(PrintStream),现在因为它们flag包含在两个线程之间sysouts,它们正在获取和释放相同的锁,那么flag值将从缓存中刷新并在内存中更新其他线程可以看到它。

因此,由于模拟比赛很困难,理论上是否有可能这段代码是线程安全的,线程 2 会看到对 的更改flag

如果是,那么volatile使用 可以达到相同的效果System.out.println();吗?

4

2 回答 2

2

JLS 关于这个话题有以下说法An unlock on a monitor happens-before every subsequent lock on that monitor.:(17.4.5

您可以在 JLS 中阅读之前发生的确切定义等,但这基本上意味着线程中在解锁锁之前发生的每次读取和写入都将被另一个获取锁的线程看到(小心!如果有一个线程 3 在没有获取锁的情况下写入标志,则不需要同步)

由于在这种情况下您锁定的是同一个对象,这意味着是的,可以保证flag线程 2 必须看到更新的值。

虽然我在文档中没有看到任何提及保证 PrintStream 获得锁并且是线程安全的使用,所以你依赖这里的实现细节(虽然不太可能被破坏)。

于 2013-08-31T12:24:04.833 回答
1

System.out.println()当有人在他们的代码中出现间歇性问题时,通常会出现与线程安全行为相关的问题,而当他们添加“调试”println()时,问题就会消失。与 println 的同步发生的缓存一致存储使错误“不可见”....但是....

...它不会使错误消失,它只会使发生的可能性大大降低,并且出于实际目的,错误已经消失了。但是,您删除 println 并且错误又回来了。

在您的代码示例中,您有两个带有布尔值的线程。当我阅读您的代码时,我认为所需的行为(线程安全)将是以下线程 2 中的行为:

Thread 2:
System.out.println();
while(flag){
    System.out.println(); // PL2 - PrintLine2
}

代码的意图是第二个 println 应该只在标志为真时发生。

换句话说,在循环中,如果该行是(将标志值添加到 println):

System.out.println(flag); // PL2 - PrintLine2

那么打印的结果应该总是“真”——绝不是假的。

情况似乎如此,但这只是因为这段代码是微不足道的......如果在 println 之前发生任何重要的“工作”,那么它变得更加明显......例如,考虑以下:

public class ThreadPrintln {

    private static boolean flag = true;

    private static class ThreadTwo implements Runnable {
        @Override
        public void run() {

            long sum = 1L;

            System.out.println("Thread2Start");
            while (flag) {
                // non-trivial code
                for (int i = 0; i < 100000; i++) {
                    sum += i;
                }
                System.out.println(flag); // PL2 - PrintLine2
            }
            System.out.println("Sum is " + sum);

        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new ThreadTwo());
        t.start();
        System.out.println("Main About to wait");
        Thread.sleep(1000);
        System.out.println("Main about to set");
        flag = false;
        System.out.println("Main about to join");
        t.join();
        System.out.println("Main about to exit");
    }
}

当我在笔记本电脑上运行它时,我得到了结果:

true
true
true
....
.... many true values
....
true
true
true
Main about to set
Main about to join
false
Sum is 38724612750001
Main about to exit

** 请注意,它在倒数第二行打印了“假”!!**

使用 println() 不会使逻辑线程安全!

随着“非平凡”代码变得越来越少,它减少了您获得不良行为的机会,但并没有消除它。删除“非平凡”代码并不能消除线程异常行为的可能性,它只会使它变得如此之少,以至于您在测试时不太可能看到它......但是,在某些时候它出现异常行为。

于 2013-08-31T15:50:14.007 回答