不稳定的保证和无序执行

发布于 2024-08-25 14:19:13 字数 1521 浏览 5 评论 0原文

重要编辑我知道“发生在”在两个作业发生的线程中我的问题是另一个线程是否可能当“a”仍然为空时读取“b”非空。所以我知道,如果您从与之前调用 setBothNonNull(...) 的线程相同的线程中调用 doIt() ,那么它不能抛出 NullPointerException。但是,如果一个线程从另一个线程调用 doIt() ,而不是调用 setBothNonNull(...) 呢?

请注意,这个问题仅与 易失性 关键字和 易失性 保证有关:它synchronized 关键字相关(所以请不要回答“你必须使用同步”,因为我没有任何问题需要解决:我只是想了解关于out-of-的易失性保证(或缺乏保证)订单执行)。

假设我们有一个包含两个 volatile 字符串引用的对象,这些引用被构造函数初始化为 null,并且我们只有一种方法来修改这两个字符串:通过调用 setBoth(...)< /em> 并且我们只能在之后将它们的引用设置为非空引用(只有构造函数才允许将它们设置为空)。

例如(这只是一个例子,还没有问题):

public class SO {

    private volatile String a;
    private volatile String b;

    public SO() {
        a = null;
        b = null;
    }

    public void setBothNonNull( @NotNull final String one, @NotNull final String two ) {
        a = one;
        b = two;
    }

    public String getA() {
        return a;
    }

    public String getB() {
        return b;
    }

}

setBothNoNull(...)中,分配非空参数“a”的行出现在分配非空参数的行之前“b”。

然后,如果我这样做(再一次,没有问题,接下来的问题就是):

doIt() {
    if ( so.getB() != null ) {
        System.out.println( so.getA().length );
    }
}

我的理解是否正确,由于无序执行,我可以获得 NullPointerException ?

换句话说:不能保证因为我读取了非空“b”,所以我会读取非空“a”?

因为由于乱序(多)处理器和 易失性 工作方式,“b”可以在“a”之前分配?

易失性保证写入后的读取始终会看到最后写入的值,但这里存在无序“问题”,对吗? (再次强调,提出“问题”是为了尝试理解 volatile 关键字和 Java 内存模型的语义,而不是为了解决问题)。

IMPORTANT EDIT I know about the "happens before" in the thread where the two assignments are happening my question is would it be possible for another thread to be reading "b" non-null while "a" is still null. So I know that if you're calling doIt() from the same thread as the one where you previously called setBothNonNull(...) then it cannot throw a NullPointerException. But what if one is calling doIt() from another thread than the one calling setBothNonNull(...) ?

Note that this question is solely about the volatile keyword and the volatile guarantees: it is not about the synchronized keyword (so please don't answer "you must use synchronize" for I don't have any issue to solve: I simply want to understand the volatile guarantees (or lack of guarantees) regarding out-of-order execution).

Say we have an object containing two volatile String references that are initialized to null by the constructor and that we have only one way to modify the two String: by calling setBoth(...) and that we can only set their references afterwards to non-null reference (only the constructor is allowed to set them to null).

For example (it's just an example, there's no question yet):

public class SO {

    private volatile String a;
    private volatile String b;

    public SO() {
        a = null;
        b = null;
    }

    public void setBothNonNull( @NotNull final String one, @NotNull final String two ) {
        a = one;
        b = two;
    }

    public String getA() {
        return a;
    }

    public String getB() {
        return b;
    }

}

In setBothNoNull(...), the line assigning the non-null parameter "a" appears before the line assigning the non-null parameter "b".

Then if I do this (once again, there's no question, the question is coming next):

doIt() {
    if ( so.getB() != null ) {
        System.out.println( so.getA().length );
    }
}

Am I correct in my understanding that due to out-of-order execution I can get a NullPointerException?

In other words: there's no guarantee that because I read a non-null "b" I'll read a non-null "a"?

Because due to out-of-order (multi)processor and the way volatile works "b" could be assigned before "a"?

volatile guarantees that reads subsequent to a write shall always see the last written value, but here there's an out-of-order "issue" right? (once again, the "issue" is made on purpose to try to understand the semantics of the volatile keyword and the Java Memory Model, not to solve a problem).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

似梦非梦 2024-09-01 14:19:13

不,您永远不会获得 NPE。这是因为易失性还具有引入先发生关系的记忆效应。换句话说,它将阻止重新排序

a = one;
b = two;

上述语句,不会被重新排序,并且所有线程都会观察 a 的值 one for a if b 已经具有值 two

以下是大卫·霍姆斯 (David Holmes) 对此的解释:
http://markmail.org/message/j7omtqqh6ypwshfv# query:+page:1+mid:34dnnukruu23ywzy+state:results

编辑(回应后续):
Holmes 的意思是,如果只有线程 A,编译器理论上可以进行重新排序。但是,还有其他线程,并且它们可以检测到重新排序。这就是为什么不允许编译器进行重新排序的原因。 java内存模型需要编译器专门确保没有线程会检测到这种重新排序。

但是如果有人从以下位置调用 doIt() 该怎么办?
调用线程之外的另一个线程
setBothNonNull(...) ?

不,您仍然永远不会有 NPE。易失性语义确实强加了线程间排序。这意味着,对于所有现有线程,one 的分配发生在 two 的分配之前。

No, you will never get a NPE. This is because volatile also has the memory-effect of introducing a happens-before relationship. In other words, it will prevent reordering of

a = one;
b = two;

The statements above, will not be re-ordered, and all threads will observe value one for a if b already has value two.

Here is a thread in which David Holmes explains this:
http://markmail.org/message/j7omtqqh6ypwshfv#query:+page:1+mid:34dnnukruu23ywzy+state:results

EDIT (response to the follow-up):
What Holmes is saying is, the compiler could in theory do a reorder if there were only thread A. However, there ARE other threads, and they CAN detect the reordering. That is why the compiler is NOT allowed to do that reordering. The java memory model requires the compiler specifically to make sure that no thread will ever detect such reordering.

But what if one is calling doIt() from
another thread than the one calling
setBothNonNull(...) ?

No, you will still NEVER have a NPE. volatile semantics do impose inter-thread ordering. Meaning that, for all existing thread, assignment of one happens before the assignment of two.

恏ㄋ傷疤忘ㄋ疼 2024-09-01 14:19:13

我的理解是否正确,由于无序执行,我可以获得 NullPointerException?换句话说:不能保证因为我读取了非空“b”,所以我也会读取非空“a”?

假设分配给ab的值或者非空,我认为你的理解不正确。 JLS 是这样说的:

(1) 如果 x 和 y 是同一线程的操作,并且 x 按程序顺序出现在 y 之前,则 hb(x, y)。

(2) 如果动作 x 与后续动作 y 同步,那么我们也有 hb(x, y)。

(3) 如果 hb(x, y) 和 hb(y, z),则 hb(x, z)。

(4) 对易失性变量的写入 (§8.3.1.4) vv 的所有后续读取同步由任何线程(其中后续是根据同步顺序定义的)。

定理

假设线程 #1 已调用 setBoth(...); 一次,并且参数非空,并且线程 #2 已观察到 b如果为非空,那么线程 #2 就无法观察到 a 为空。

非正式证明

  1. By (1) - hb(write(a, non-null), write(b, non-null)) in thread #1
  2. By ( 2) 和 (4) - hb(写(b, 非空), 读(b, 非空))
  3. 通过 (1) -线程 #2 中的 hb(read(b, non-null), read(a, XXX)),
  4. By (4) - hb(write(a, non-null), read(b, non-null))
  5. By (4) - hb(write(a, non-null), read(a, XXX))

换句话说,将非空值写入 a “发生在”读取 a 的值 (XXX) 之前。 XXX 可以为 null 的唯一方法是,如果有其他某个操作将 null 写入 a,则 hb(write(a,non-null), write(a,XXX)) 和 hb(写(a,XXX),读(a,XXX))。根据问题定义这是不可能的,因此 XXX 不能为空。量子ED。

解释 - JLS 指出 hb(...)(“发生在之前”)关系并不完全禁止重新排序。但是,如果 hb(xx,yy),则仅当生成的代码具有与原始序列相同的可观察效果时,才允许对操作 xx 和 yy 进行重新排序。

Am I correct in my understanding that due to out-of-order execution I can get a NullPointerException? In other words: there's no guarantee that because I read a non-null "b" I'll read a non-null "a"?

Assuming that the values assigned to a and b or non-null, I think your understanding is not correct. The JLS says this:

(1) If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

(2) If an action x synchronizes-with a following action y, then we also have hb(x, y).

(3) If hb(x, y) and hb(y, z), then hb(x, z).

and

(4) A write to a volatile variable (§8.3.1.4) v synchronizes-with all subsequent reads of v by any thread (where subsequent is defined according to the synchronization order).

Theorem

Given that thread #1 has called setBoth(...); once, and that the arguments were non-null, and that thread #2 has observed b to be non-null, then thread #2 cannot then observe a to be null.

Informal Proof

  1. By (1) - hb(write(a, non-null), write(b, non-null)) in thread #1
  2. By (2) and (4) - hb(write(b, non-null), read(b, non-null))
  3. By (1) - hb(read(b, non-null), read(a, XXX)) in thread #2,
  4. By (4) - hb(write(a, non-null), read(b, non-null))
  5. By (4) - hb(write(a, non-null), read(a, XXX))

In other words, the write of a non-null value to a "happens-before" the read of the value (XXX) of a. The only way that XXX can be null, is if there was some other action writing null to a such that hb(write(a,non-null), write(a,XXX)) and hb(write(a,XXX), read(a,XXX)). And this is impossible according to the problem definition, and therefore XXX cannot be null. QED.

Explanation - the JLS states that the hb(...) ("happens-before") relationship does not totally forbid reordering. However, if hb(xx,yy), then reordering of actions xx and yy is only allowed if the resulting code has the same observable effect as the original sequence.

千笙结 2024-09-01 14:19:13

我发现下面的文章解释了在这种情况下 易失性 与同步具有相同的排序语义。 Java Volatile 很强大

I found the following post that explains that volatile has the same ordering semantics as synchronized in this case. Java Volatile is Powerful

感悟人生的甜 2024-09-01 14:19:13

虽然 Stephen C 的和已接受的答案很好并且几乎涵盖了它,但值得注意的是变量 a 不必是易失性的 - 并且您仍然不会得到 NPE。
这是因为,无论 a 是否为 易失性,a = oneb = Two 之间都会存在发生前关系。所以 Stephen C 的形式证明仍然适用,只是 a 不需要是易失性的。

While Stephen C's and the accepted answers are good and pretty much cover it, it's worth making an important note that variable a doesn't have to be volatile - and you still won't get an NPE.
This is because there will be a happens-before relationship between a = one and b = two, regardless of whether a is volatile. So Stephen C's formal proof still applies, just no need for a being volatile.

鹊巢 2024-09-01 14:19:13

我读了这个页面并发现了一个非- 挥发性和您的问题的非同步版本:

class Simple {
    int a = 1, b = 2;
    void to() {
        a = 3;
        b = 4;
    }
    void fro() {
        System.out.println("a= " + a + ", b=" + b);
    }
}

fro 可能会获取 1 或 3 作为 a 的值,并且独立地可能会获取 2 或 4 作为 的值b。

(我意识到这并不能回答你的问题,但它补充了你的问题。)

I read this page and found a non-volatile & non-synchronized version of your question:

class Simple {
    int a = 1, b = 2;
    void to() {
        a = 3;
        b = 4;
    }
    void fro() {
        System.out.println("a= " + a + ", b=" + b);
    }
}

fro may obtain either 1 or 3 for the value of a, and independently may obtain either 2 or 4 for the value of b.

(I realize this doesn't answer your question but it complements it.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文