2

Let's say we have a class

class Foo {
    int x;
    Foo() {
        x = 5;
    }
}

and some client code

public static void main(String[] args) {
    Foo foo = new Foo();
    new Thread(() -> {
        while (true) {
            new Thread(() -> {
                if (foo.x != 5) {
                    throw new AssertionError("this statement is false 1");
                }
                new Thread(() -> {
                    if (foo.x != 5) {
                        throw new AssertionError("this statement is false 2");
                    }
                }).start();
            }).start();
        }
    }).start();
}

Is it impossible for an AssertionError to be thrown because happens-before is transitive?

Even though Foo's x is not final, because of the happens-before guarantee of Thread.start(), a newly created thread from the thread that instantiated Foo, will see all the updates up to having called Thread.Start().

However, this thread also spawns many children threads, and since there is a happens-before relationship again, can we say that because of the transitive property of the happens-before, that AssertionError could never be thrown?

katiex7
  • 863
  • 12
  • 23

1 Answers1

4

Your question:

Since there is a happens-before relationship again, can we say that because of the transitive property of the happens-before, that AssertionError could never be thrown?

The answer is yes. As we can see in the JLS8 section 17.4.5. Happens-before Order:

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

It is also given in the same section of the JLS that:

  • A call to start() on a thread happens-before any actions in the started thread.

So there is

  • hb(new Foo(), first-action-in-first-thread) and
  • hb(first-action-in-first-thread, first-action-in-first-assertion-thread)
  • hb(first-action-in-first-thread, first-action-in-second-assertion-thread)

which means that there is also:

  • hb(new Foo(), first-action-in-first-assertion-thread)
  • hb(new Foo(), first-action-in-second-assertion-thread)

(Since "For each thread t, the synchronization order of the synchronization actions (§17.4.2) in t is consistent with the program order (§17.4.3) of t.", I can omit the steps in-between, like the while(true) loop)

Erwin Bolwidt
  • 30,799
  • 15
  • 56
  • 79
  • beautiful! this `If hb(x, y) and hb(y, z), then hb(x, z)` actually explains it. 1+ – Eugene Jan 15 '18 at 08:53
  • By the way, rereading the answer again, when you quoted the JLS "(Since "For each thread ... can omit the steps in-between, like the while(true) loop)" I took it as, whenver you have some synchronization action such as Thread.start(), then those things cannot be reordered, so everything that should logically come first(whether lines above or lines evaluated right to left) in a single thread will have run e.g Foo x = new Foo(); new Thread(() ->{}).start(); new Thread(() ->{}).start(); That Foo is always first evaluated before the next two threads and also the Runnable lambda – katiex7 Jan 15 '18 at 20:26
  • @katiex7 - Compilers are allowed to reorder the instructions in either thread, when this does not affect the execution of that thread in isolation. JLS 17.4. Memory Model – Farhan stands with Palestine Nov 25 '18 at 13:12
  • Does the construction call `x = 5;` hb `Foo foo = new Foo()`.? – Farhan stands with Palestine Nov 28 '18 at 14:28