Search code examples
javajava-memory-model

How does the happen-before property relate to visibility and ordering?


I'd like some clarity on what exactly the happens-before property means.

I've seen explanations for the happens-before property say that updates to global variables (which are not volatile or enclosed in a sync block) are made visible to other threads if they are altered before some other variable which is volatile or altered within a sync block. Is this right? If so, where in the java documentation does it say this?

My understanding was that the happens-before property defines the relationship between shared fields and code execution e.g:

  • an unlock of a monitor happens-before every subsequent lock of the same monitor.
  • a write to a volatile field happens-before every subsequent read of that same field.
  • a call to start on a thread happens-before any action in the started thread.
  • all actions in a thread happen-before any other thread successfully returns from a join on that thread.

For example:

class Shared {

  private int y = 0;
  private volatile int x = 0;

  public void setOne() {
    y = 1;
    x = 1;
  }

  public int getY() {
    return y;
  }

}

For the code above, given 2 threads:

Shared shared = new Shared();

new Thread(() -> shared.setOne()).start();
new Thread(() -> shared.getY()).start();

Edit Assuming we can guarantee that the first thread has started, would getY() return 0 or 1 here?

I've also seen examples saying that this behavior happens only following a read of a volatile field in a thread. So in that case if one thread reads the value of the volatile field (let's say thread B), then all fields written before that volatile field in thread A are available to thread B. According this, if I modify the getY() method in Shared object from above to be:

  public int getXPlusY() {
    int local = x;
    return local + y;
  }

Is it this action that makes y visible to the other thread?


Solution

  • Let's look at your second example first.

    class Shared {
    
      private int y = 0;
      private volatile int x = 0;
    
      public void setOne() {
        y = 1; //(1)
        x = 1; //(2)
      }
    
      public int getXPlusY() {
        int local = x; //(3)
        return local + y; //(4)
      }
    }
    

    We know that there is a happens-before relationship between (1) and (2) due to program order:

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

    Since x is volatile, we know that there's a happens-before relationship between (2) and (3)

    A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.

    And there is a happens-before relationship between (3) and (4) due to program order again:

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

    Therefore, we have a happens-before chain (1) → (2), (2) → (3), (3) → (4)

    And since happens-before is a transitive relation (if A happens before B and B happens before C, then A happens before C) that means that (1) has a happens-before relation with (4).

    Now let's look at the first example:

    class Shared {
    
      private int y = 0;
      private volatile int x = 0;
    
      public void setOne() {
        y = 1; //(1)
        x = 1; //(2)
      }
    
      public int getY() {
        return y; //(3)
      }
    }
    

    There's again a happens-before relation between (1) and (2), but that's about it. Since x is not read by the second thread, we have no happens-before between (2) and (3). Therefore we have no happens-before relation between (1) and (3).

    The quotes were taken from Chapter 17 of the Java Language Specification (JLS)