简体   繁体   English

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

[英]Why this code finishes if getter is marked as synchronized?

Why this code successfully finishes when method get() is marked as synchronized despite the fact that field value is not volatile? 尽管字段不是易变的,但是当方法get()被标记为已同步时,为什么此代码成功完成? Without synchronized it runs indefinitely on my machine (as expected). 如果没有同步,它将无限期地在我的机器上运行(如预期的那样)。

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

Synchronization forces this.value = value to happen before get() . 同步强制this.value = value get() 之前发生

It ensures the visibility of the updated value. 它确保更新值的可见性。

Without synchronization, there is no such guarantee. 没有同步,就没有这样的保证。 It might work, it might not. 可能会起作用,也可能不起作用。

@Andy Turner is partly correct. @Andy Turner部分正确。

The addition of the synchronized on the get() method is affecting the memory visibility requirements, and causing the (JIT) compiler to generate different code. get()方法上添加synchronized会影响内存可见性要求,并导致(JIT)编译器生成不同的代码。

However, strictly speaking there needs to be a happens before relationship connecting the set(...) call and the get() call. 但是,严格地说, 连接set(...)调用和get()调用之间的关系之前需要发生 That means that the set method should be synchronized as well as get (if you are going to do it this way!). 这意味着set方法应该synchronized以及get (如果你打算这样做的话!)。

In short, the version of your code that you observed to be working is NOT guaranteed to work on all platforms, and in all circumstances. 简而言之,您观察到的代码版本无法保证在所有平台上以及在所有情况下都能正常工作。 In fact, you got lucky! 事实上,你很幸运!


Reading between the lines, it seems that you are trying to work out how Java's memory model works by experimentation. 阅读这些内容之后,您似乎正在尝试通过实验来研究Java的内存模型是如何工作的。 This is not a good idea. 这不是一个好主意。 The problem is that you are trying to reverse engineer an immensely complicated black box without enough "input parameters" 1 for you to vary to cover all potential aspects of the black boxes behavior. 问题是你正试图对一个非常复杂的黑匣子进行反向工程,没有足够的“输入参数” 1 ,你可以改变以涵盖黑匣子行为的所有潜在方面。

As a result the "learning by experiment" approach is liable to leave you with an incomplete or mistaken understanding. 因此,“通过实验学习”的方法可能会让您理解不完整或错误。

If you want a complete and accurate understanding, you should start by reading about the Java Memory Model in a good text book ... or the JLS itself. 如果您想要完整和准确的理解,您应该首先阅读一本好的教科书中的Java内存模型......或者JLS本身。 By all means, use experimentation to try to confirm your understanding, but you do need to be aware that the JMM specifies (guarantees) only what happens if you do the right thing. 通过各种方式,使用实验来尝试确认您的理解,但您需要知道JMM 指定(保证)如果您做正确的事情会发生什么。 If you do the wrong thing, your code may work anyway ... depending on all sorts of factors. 如果你做错了,你的代码可能会工作......取决于各种因素。 Hence, it is often difficult to get experimental confirmation that a particular way of doing things is either correct or incorrect 2 . 因此,通常很难得到实验确认某种特定的做事方式是正确的还是不正确的2

1 - Some the parameters you would need don't actually exist. 1 - 您需要的一些参数实际上并不存在。 For example, the one that allows you to run Java N for N > 12, or the one that allows you to run on hardware that you don't have access ... or that doesn't yet exist. 例如,允许您为N> 12运行Java N的那个,或者允许您在无法访问的硬件上运行的那个...或者尚不存在的硬件。

2 - As is illustrated by your example. 2 - 如您的示例所示。 You are getting the "right" answer, even though the code is wrong. 即使代码错误,您也会得到“正确”的答案。

For starters, either value needs to be volatile or both get and set need to be synchronized for this to be correct. 对于初学者来说,要么value必须是volatile ,要么需要synchronized getset ,以使其正确。

JLS 17.4.5: JLS 17.4.5:

An unlock on a monitor happens-before every subsequent lock on that monitor. 监视器上的解锁发生在该监视器上的每个后续锁定之前。

It is possible for value to be set to 5 prior to the lock being released, which puts it before the happens-before edge and makes it available the next time the lock is acquired. 在释放锁定之前可以将value设置为5 ,这使得它在发生之前的边缘之前设置为在下次获取锁定时使其可用。

It should be noted that such guarantees are fragile and depending on the thread scheduler, may not exist at all. 应该注意的是,这种保证是脆弱的,并且取决于线程调度器,可能根本不存在。 On platforms with weaker synchronization models, you may not see the same effects you see here. 在具有较弱同步模型的平台上,您可能看不到您在此处看到的相同效果。


See also: Loop doesn't see changed value without a print statement 另请参见: 如果没有print语句,循环不会看到更改的值

Strange behavior of a Java thread associated with System.out 与System.out关联的Java线程的奇怪行为

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM