Search code examples
language-lawyervolatilejava-memory-modelhappens-beforedata-race

Why is this Java program containing two volatile writes data race free?


Consider the following Java program:

static volatile int shared;

public static void main(final String[] args) {
  final Runnable r = () -> {
    shared = 1;
  };

  new Thread(r).start();
  new Thread(r).start();
}

Because shared is marked volatile, I want to say that this program is free from data races. However, how is this to be motivated based on the JLS (say, version 11)?

In chapter 17 we are told:

When a program contains two conflicting accesses (§17.4.1) that are not ordered by a happens-before relationship, it is said to contain a data race.

I take this to be the definition of data races provided by the JLS. Then we have section 17.4.1 telling us:

Two accesses to (reads of or writes to) the same variable are said to be conflicting if at least one of the accesses is a write.

Ok, so we have conflicting accesses here as we have two writes to shared. Now we must have a happens-before relationship between the two writes, otherwise we have a race. I have, however, not managed to find why we have such relation here. Chapter 17 tells me that:

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

And the same chapter tells me:

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

But I have not managed to find anything happens-before relating writes to volatile variables. Why is that?


Solution

  • Accesses to a volatile variable are never subject to a data race.

    It is not so obvious from the JLS, but in the phrase

    (§17.4.1) Two accesses to (reads of or writes to) the same variable are said to be conflicting if at least one of the accesses is a write.

    the terms "read" and "write" are not generic ones, but are described in the next section "17.4.2 Actions" as accesses to a non-volatile variable:

    Read (normal, or non-volatile). Reading a variable.

    Write (normal, or non-volatile). Writing a variable.

    In that section accesses to volatile variable are classified as a part of Synchronization actions, with different terms "Volatile read" and "Volatile write":

    Volatile read. A volatile read of a variable.

    Volatile write. A volatile write of a variable.

    Because only (non-volatile) reads and writes can be conflicting, only those accesses may contain a data race. Volatile reads and writes can never be conflicting and can never contain a data race.