如果将getter标记为已同步,为什么这段代码会完成?

时间:2018-09-08 19:07:11

标签: java multithreading synchronized non-volatile

为什么将方法get()标记为已同步,尽管字段 value 不变,为什么此代码成功完成?如果不同步,它会无限期地在我的机器上运行(如预期)。

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();
    }
}

3 个答案:

答案 0 :(得分:2)

同步迫使this.value = value {em> get()之前发生。

它确保了更新值的可见性。

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

答案 1 :(得分:1)

@安迪·特纳(Andy Turner)部分正确。

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

但是,严格来说,在连接set(...)调用和get()调用之前,必须有一个发生关系。这意味着set方法应该和synchronized一样get(如果您要这样做的话!)。

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


在这两行之间阅读,似乎您正在尝试通过实验弄清楚Java的内存模型是如何工作的。 这不是一个好主意。问题是,您正在尝试对没有足够的“输入参数” 1 非常复杂的黑盒进行反向工程让您有所不同,以涵盖黑匣子行为的所有潜在方面。

因此,“通过实验学习”的方法可能会使您产生不完整或错误的理解。

如果您想全面而准确地理解,您应该开始,方法是阅读一本好教科书中的Java内存模型...或JLS本身。一定要通过实验来尝试确认您的理解,但是您确实需要知道JMM仅(保证)指定 如果您做正确的事情会发生什么。如果您做错了事,您的代码可能仍然可以工作……取决于各种因素。因此,通常很难获得实验性的证实,即某种特定的做事方式是正确的还是不正确的 2

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

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

答案 2 :(得分:0)

对于初学者来说,value必须为volatile,或者getset都必须为synchronized,以确保正确。

JLS 17.4.5:

  

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

可以在释放锁定之前将value设置为5,这会将它放置在 happens-before 边缘之前,并使其在下一个可用获取锁的时间。

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


另请参阅: Loop doesn't see changed value without a print statement

Strange behavior of a Java thread associated with System.out