Java同步线程澄清

3
这个程序的答案需要在5秒后变成“Changing done”,但是我得到的却是“Changing done”和“DONE”。我的getDone方法没有同步。你有没有想法,我做了哪些让线程完成处理的事情。
public class Main {
    private static boolean done = false;
    private static int count;

    public static void main(String[] args)throws InterruptedException {
        new Thread(() -> {
            while (!getDone()) {
                count = count + 1;
            }
            System.out.println("DONE!!!!");
        }).start();
            Thread.sleep(5000);
        System.out.println("Changing done");
        synchronized (Main.class) {
            done = true;
        }
    }

    public static boolean getDone() {
        return done;
    }
}

2
你的程序正如我所预期的那样运行。你是否预期你的匿名Runnable不会完成? - CodeBlind
@CodeBlind:访问done的同步不正确,调用getDone()可能无法看到当前值。 - user140547
1
这里最糟糕的情况是工作线程可能无法立即看到“done”中的更改,但它肯定最终会看到。由于只有一个线程会更改“done”,因此您可以在其声明中使用“volatile”关键字,并完全取消使用“synchronized”块。但这都是过去的事了 - 我仍然不明白OP的意图。他似乎对工作线程打印“DONE!!!”感到惊讶。 - CodeBlind
1
@CodeBlind:如果你不使用volatile或synchronize或其他同步机制,线程可能永远看不到更新后的值。请参见https://dev59.com/C3VD5IYBdhLWcg3wDXF3 - user140547
@CodeBlind:正如我的回答所解释的那样,OP的问题(正确地)指出该代码未正确同步,但它仍然可以工作。但是,损坏的代码在许多情况下仍然可以工作,这并不意味着写入一定是不可见的。 - user140547
1
第一次运行程序就出现了完美的“永久循环”行为。这显然取决于环境... - Holger
2个回答

5

如果您没有正确同步对done的访问,这意味着您的代码可能会失败,即线程可能看不到更新的值。

这并不意味着除非正确同步,否则该值一定不可见。因此,在许多情况下,仍然可以看到对done的写入(即使是破损的代码在许多情况下仍然可以工作,这使得并发编程更加困难)。只是不能保证在每种情况下都能正常工作。


将“完成”状态的更改在同步块中执行。它将是可见的。这在JLS的内存模型下有所说明。您是否有特定的参考资料来证明线程可能无法看到更新后的值?或者也许在我包含的参考资料中,但经过粗略阅读,我认为对“完成”的更改将是可见的。 - matt
@matt:不仅写入需要同步,读取也需要同步。你不需要JLS来做到这一点。例如,请参见https://dev59.com/12ox5IYBdhLWcg3weT-e中的引用(重点在于我):当同步方法退出时,它会自动与**同一对象的任何后续调用同步方法建立happens-before关系**。 - user140547
我并不是说不存在竞态条件。使用synchronized关键字可以确保多个线程之间的变化是可见的。这与声明变量为volatile类似。 - matt
1
@matt:不是这样的。当变量被声明为“volatile”时,对同一“volatile”变量进行的写入和读取都会建立一个“happens-before”关系。仅在写入上执行synchronized并不能保证任何事情。 - Holger
@Holger 我认为这里的相关标准不是happens-before。17-3给出了一个等效的例子。编译器可以自由地只读取一次字段this.done,并在每次循环执行中重用缓存值。 这将与你们两个都一致。我理解同步语句会导致跨线程的内存同步。这听起来是错误的,除非我找到了导致我得出这个结论的语句。 - matt
3
@matt:确实如此,但只适用于已建立的“先行发生”关系,即一个线程离开synchronized块所做的写入操作保证可以被随后进入相同对象的synchronized块的其他线程读取。否则,拥有缓存值的线程如何知道另一个线程执行了synchronized块呢? - Holger

1
如您所提到的,没有显式的内存同步将旋转线程看到的“done”与主线程进行同步。虽然当退出“synchronized”块时,主线程会跨越写内存屏障,但是旋转线程没有跨越显式读内存屏障。
但是,有许多方法可以使线程看到更新的信息。如果操作系统将线程从其运行处理器中交换出去,则缓存的内存可能会丢失,因此当线程被交换回来时,它将从中央内存请求“done”,并将看到更新。
此外,尽管您的示例代码没有显示,但如果调用其他“synchronized”方法(例如“System.out.println()”)或跨越其他内存屏障(访问另一个“volatile”字段),则这也会导致“done”被更新。

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