1

get()尽管字段不是易失性的,为什么当方法被标记为同步时此代码成功完成?如果没有同步,它会在我的机器上无限期地运行(如预期的那样)。

public class MtApp {

    private int value;

    /*synchronized*/ int get() {
        return value;
    }

    void set(int value) {
        this.value = value;
    }

    public static void main(String[] args) throws Exception {
        new MtApp().run();
    }

    private void run() throws Exception {
        Runnable r = () -> {
            while (get() == 0) ;
        };
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(10);
        set(5);
        thread.join();
    }
}
4

3 回答 3

2

同步力this.value = value发生在之前 get()

它确保更新值的可见性。

没有同步,就没有这样的保证。它可能有效,也可能无效。

于 2018-09-08T19:21:06.740 回答
1

@Andy Turner 部分正确。

方法的添加synchronized影响get()了内存可见性要求,并导致(JIT)编译器生成不同的代码。

但是,严格来说,需要在连接呼叫和呼叫的关系之前发生。这意味着该方法应该是一样好(如果你打算这样做!)。set(...)get()setsynchronizedget

简而言之,您观察到的代码版本不能保证在所有平台和所有情况下都可以工作。事实上,你很幸运!


从字里行间看,您似乎正在尝试通过实验来弄清楚 Java 的内存模型是如何工作的。 这不是一个好主意。 问题是您正试图对一个极其复杂的黑盒进行逆向工程,而没有足够的“输入参数” 1让您改变以涵盖黑盒行为的所有潜在方面。

因此,“通过实验学习”的方法很容易让您产生不完整或错误的理解。

如果您想要一个完整而准确的理解,您应该首先阅读一本好教科书中的 Java 内存模型……或者 JLS 本身。无论如何,使用实验来尝试确认您的理解,但您确实需要知道 JMM指定(保证)如果您做正确的事情会发生什么。如果你做错了事,你的代码可能仍然有效......取决于各种因素。因此,通常很难通过实验确认某种特定的做事方式是正确的还是不正确的2

1 - 您需要的某些参数实际上并不存在。例如,允许您在 N > 12 的情况下运行 Java N 的一种,或者允许您在您无权访问的硬件上运行的一种……或者尚不存在的硬件。

2 - 如您的示例所示。即使代码错误,您也会得到“正确”的答案。

于 2018-09-09T00:50:47.893 回答
0

对于初学者来说,要么value需要volatile要么两者都需要,get并且set需要synchronized是正确的。

JLS 17.4.5:

监视器上的解锁发生在该监视器上的每个后续锁定之前。

可以将 forvalue设置为5在释放锁之前,这会将其置于发生前边缘之前,并使其在下次获取锁时可用。

应该注意的是,这样的保证是脆弱的,并且取决于线程调度程序,可能根本不存在。在具有较弱同步模型的平台上,您可能看不到您在此处看到的相同效果。


另请参阅: 没有打印语句,循环看不到更改的值

与 System.out 关联的 Java 线程的奇怪行为

于 2018-09-08T20:44:20.047 回答