10

I'm trying to understand Java happens-before order concept and there are a few things that seem very confusing. As far as I can tell, happens before is just an order on the set of actions and does not provide any guarantees about real-time execution order. Actually (emphasize mine):

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

So, all it says is that if there are two actions w (write) and r (read) such that hb(w, r), than r might actually happens before w in an execution, but there's no guarantee that it will. Also the write w is observed by the read r.

How I can determine that two actions are performed subsequently in run-time? For instance:

public volatile int v;
public int c;

Actions:

Thread A
v = 3;  //w

Thread B
c = v;  //r

Here we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3? Does synchronization order provide such guarantees?

Eugene
  • 117,005
  • 15
  • 201
  • 306
St.Antario
  • 26,175
  • 41
  • 130
  • 318

4 Answers4

15

When the JLS says that some event X in thread A establishes a happens before relationship with event Y in thread B, it does not mean that X will happen before Y.

It means that IF X happens before Y, then both threads will agree that X happened before Y. That is to say, both threads will see the program's memory in a state that is consistent with X happening before Y.


It's all about memory. Threads communicate through shared memory, but when there are multiple CPUs in a system, all trying to access the same memory system, then the memory system becomes a bottleneck. Therefore, the CPUs in a typical multi-CPU computer are allowed to delay, re-order, and cache memory operations in order to speed things up.

That works great when threads are not interacting with one another, but it causes problems when they actually do want to interact: If thread A stores a value into an ordinary variable, Java makes no guarantee about when (or even if) thread B will see the value change.

In order to overcome that problem when it's important, Java gives you certain means of synchronizing threads. That is, getting the threads to agree on the state of the program's memory. The volatile keyword and the synchronized keyword are two means of establishing synchronization between threads.


I think the reason they called it "happens before" is to emphasize the transitive nature of the relationship: If you can prove that A happens before B, and you can prove that B happens before C, then according to the rules specified in the JLS, you have proved that A happens before C.

Solomon Slow
  • 25,130
  • 5
  • 37
  • 57
  • The [JLS, 17.4.5](https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5) says: "If one action happens-before another, then the first is visible to and ordered before the second.". So I think it's misleading to say that "IF X happens before Y, then both threads will agree...". Rather, "happens before" really means "for all intents and purposes, X happens before Y". Some reordering is allowed "if the reordering produces results consistent with a legal execution" (JLS again), but that does *not* mean that the happens before relationship is conditioned on anything. – Just Me Dec 15 '20 at 20:43
  • A little further down: "An unlock on a monitor happens-before every subsequent lock on that monitor." which is implicitly conditional (depends on the order of locking and unlocking). But it does seem that the notion of "happens before" refers to an *unconditional* relationship, albeit one that is only fully discovered at runtime. As someone who's just getting into this, I found your answer enlightening, but then realized it was confusing, until finally I decided to just read the JLS. I think clarifying this point in your answer may save future readers some trouble... – Just Me Dec 15 '20 at 21:02
  • My point is, it's not as misleading as you make it out to be. It actually does mean "happens before" (up to some reordering freedom, which you are guaranteed not to notice). – Just Me Dec 15 '20 at 21:46
  • OK, I am not going to change my answer because the whole question has been closed. But would it have made more sense to you if, in my second paragraph, I had said, "It means that _IF_ the event X _actually does_ happen _in real-time_ before event Y happens, then both threads will agree upon the actual order of the two events?" That is to say, was your confusion due to the fact that I used the phrase "happens before" to describe one event happening before another event instead of using the phrase as the _name_ of a special relationship, the way the JLS uses it? – Solomon Slow Dec 15 '20 at 22:17
  • not really. As I understand it, the relationship "X happens before Y" means that any type of experiment (in any thread/s whatsoever) involving X and Y will think that X occurred before Y. The IF you introduce is not needed, and in fact is misleading: X may even occur *after* Y "in real time", but only if it will appear to anyone bothering to look that X occurred before Y. The following example (JLS again) may help: "the write of a default value to every field of an object constructed by a thread need not happen before the beginning of that thread, as long as no read ever observes that fact." – Just Me Dec 15 '20 at 23:16
4

I would like to associate the above statement with some sample code flow.

To understand this, let us take below class that has two fields counter and isActive.

class StateHolder {
    private int counter = 100;
    private boolean isActive = false;

    public synchronized void resetCounter() {
            counter = 0;
            isActive = true;
    }

    public synchronized void printStateWithLock() {
        System.out.println("Counter : " + counter);
        System.out.println("IsActive : " + isActive);
    }

    public void printStateWithNoLock() {
        System.out.println("Counter : " + counter);
        System.out.println("IsActive : " + isActive);
    }
}

And assume that there are three thread T1, T2, T3 calling the following methods on the same object of StateHolder:

T1 calls resetCounter() and T2 calls printStateWithLock() at a same time and T1 gets the lock
T3 -> calls printStateWithNoLock() after T1 has completed its execution

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

and the immediate line says,

As per the above statement, it gives the flexibility for JVM, OS or underlying hardware to reorder the statements within the resetCounter() method. And as T1 gets executed it could execute the statements in the below order.

    public synchronized void resetCounter() {
            isActive = true;
            counter = 0;
    }

This is inline with the statement not necessarily imply that they have to take place in that order in an implementation.

Now looking at it from a T2 perspective, this reordering doesn't have any negative impact, because both T1 and T2 are synchronizing on the same object and T2 is guaranteed to see changes changes to both of the fields, irrespective of whether the reordering has happened or not, as there is happens-before relationship. So output will always be:

Counter : 0
IsActive : true

This is as per statement, If the reordering produces results consistent with a legal execution, it is not illegal

But look at it from a T3 perspective, with this reordering it possible that T3 will see the updated value of isActive as 'truebut still see thecountervalue as100`, although T1 has completed its execution.

Counter : 100
IsActive : true

The next point in the above link further clarifies the statement and says that:

More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.

In this example T3 has encountered this problem as it doesn't have any happens-before relationship with T1 or T2. This is inline with Not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship.

NOTE: To simplify the case, we have single thread T1 modifying the state and T2 and T3 reading the state. It is possible to have

T1 updates counter to 0, later
T2 modifies isActive to true and sees counter is 0, after sometime
T3 that prints the state could still see only isActive as true but counter is 100, although both T1 and T2 have completed the execution.

As to the last question:

we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3?

public volatile int v;
public int c;

Thread A
v = 3;  //w

Thread B
c = v;  //r

Since v is a volatile, as per Happens-before Order

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

So it is safe to assume that when Thread B tries to read the variable v it will always read the updated value and c will be assigned 3 in the above code.

  • One question. Why didn't you declare the fields volatile? Is it safe to use them that way? I think the thread that performs object construction... I mean changes made by the thread ( `counter = 100` ), may not be observed by any thread invoking the object's method (no happens before relationship). Maybe I didn't exactly realize your idea...? – St.Antario Apr 03 '16 at 12:10
  • @St.Antario The [JLS#8.8.3](http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.8.3) indicate that **it would lock the object under construction, which is normally not made available to other threads until all constructors for the object have completed their work.** So per above link, threads will only see the fully intialized object only after the constructor finishes and JVM takes care of it. Unless we publish the object in the constructor and made it escape as in `Publication and Escape` section of book `Java concurrency in practice`. – Madhusudana Reddy Sunnapu Apr 03 '16 at 12:52
  • Yes it locks, but it doesn't clearly specified that the lock acts percisely as `synchronized { ... }`. I thought they meant that the construction operation is atomic and that's it. No memory visibility guarantees provided. – St.Antario Apr 03 '16 at 17:47
  • @St.Antario Specsays that _it would lock the object under construction_. Although not explicit, I would read it as, having a lock ensures it is atomic as well as memory visibility guarantees. So once the constructor completes object initialization, the other threads will see the fully initialized internal state of the object. – Madhusudana Reddy Sunnapu Apr 04 '16 at 03:59
  • Unfortunately, we can't say so. I consulted __JCIP, section 3.5 Safe publication__ and there was an example using unsafe publication of `Holder` (has the only public `int` field). It's said that _But far worse, other threads could see an up to date value for the holder reference, but stale values for the state of the `Holder`._ – St.Antario Apr 04 '16 at 13:18
  • @St.Antario That is true. Infact, if you look at my earlier comment it says _Unless we publish the object in the constructor and made it escape as in Publication and Escape section of book Java concurrency in practice_. It is not the issue with the constructor, but the object reference `holder` is not published safely. But in your example the `StateHolder` is not escaped. We create the `StateHolder` object and pass it to threads T1, T2 & T3. – Madhusudana Reddy Sunnapu Apr 04 '16 at 13:38
  • Yes, it's not. But I still don't quite agree that even if we publish the object to a thread using synchronization on the object's intrinsic lock we're guaranteed to see fresh values of its fields. Unfortunately, it's not specified in JLS clearly. You said that the lock is used when contructing the object, but which one? So, I would balance to say that we have to declare fileds either `final` or `volatile` to ensure visibilty of correct values. Moreover the [JLS 17.5](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5). Such guarantees is percisely `final` field semantic. – St.Antario Apr 05 '16 at 09:54
  • @St.Antario Per JLS#8.8.3 _it would lock the object under construction, which is normally not made available to other threads until all constructors for the object have completed their work_ , unless the object escapes, I still feel or interpret final may not be required for constructor to publish the fully constructed object state, although it doesn't explicitly specified that. If I find something that is more concrete in either way, would share and that might help.. – Madhusudana Reddy Sunnapu Apr 05 '16 at 10:33
  • @St.Antario I have looked at the source code of `StringBuffer` and `ThreadGroup` to see if variables needs to be marked `final`. It turned out that they didn't the fields as final although they are initialized in `constructor`. It gives a good indication that there is no need to mark the fields as `final` unless needed and object creation using constructor ensures the memory visibility and atomicity guarantees. And also marking a field as `final` makes it not to change the value later which could be seen as a restriction in most case if we want to change its value through setters later. – Madhusudana Reddy Sunnapu Apr 05 '16 at 14:02
  • I have a question about your explanation about the term **not necessarily imply that they have to take place in that order in an implementation.** In your example I can induce that **counter = 0;** happens before **isActive = true;**.But why? In the [jsl](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.5) there is an example under this term.**For example, the write of a default value to every field of an object constructed by a thread need not happen before the beginning of that thread, as long as no read ever observes that fact.**Different situation with your example. – lin Jul 27 '17 at 03:06
  • _So it is safe to assume that when Thread B tries to read the variable v it will always read the updated value and c will be assigned 3 in the above code._ — This is incorrect. It can be `c = 0`. – v.ladynev Jan 28 '18 at 17:22
2

Interpreting @James' answer to my liking:

// Definition: Some variables
private int first = 1;
private int second = 2;
private int third = 3;
private volatile boolean hasValue = false;

// Thread A
first = 5;
second = 6;
third = 7;
hasValue = true;

// Thread B
System.out.println("Flag is set to : " + hasValue);
System.out.println("First: " + first);  // will print 5
System.out.println("Second: " + second); // will print 6
System.out.println("Third: " + third);  // will print 7

if you want the state/value of the memory(memory and CPU cache) seen at the time of a write statement of a variable by one thread,

State of the memory seen by hasValue=true(write statement) in Thread A :

first having value 5,second having value 6,third having value 7

to be seen from every subsequent(why subsequent even though only one read in Thread B in this example? We many have Thread C doing exactly similar to Thread B) read statement of the same variable by another thread,then mark that variable volatile.

If X (hasValue=true) in Thread A happens before Y (sysout(hasValue)) in Thread B, the behaviour should be as if X happened before Y in the same thread (memory values seen at X should be same starting from Y)

user104309
  • 690
  • 9
  • 20
2

Here we have hb(w, r) but that doesn't mean that c will contain value 3 after assignment. How do I enforce that c is assigned with 3? Does synchronization order provide such guarantees?

And your example

public volatile int v;
public int c;
Actions:

Thread A
v = 3;  //w

Thread B
c = v;  //r

You don't need volatile for v in your example. Let's take a look at a similar example

int v = 0;
int c = 0;
volatile boolean assigned = false;

Actions:

Thread A

v = 3;
assigned = true;

Thread B

while(!assigned);
c = v;
  1. assigned field is volatile.
  2. We will have c = v statement in Thread B only after assigned will be true (while(!assigned) is responsible for that).
  3. if we have volatile — we have happens before.
  4. happens before means that, if we see assigned == true — we will see all that happened before a statement assigned = true: we will see v = 3.
  5. So when we have assigned == true -> we have v = 3.
  6. We have c = 3 as a result.

What will happen without volatile

int v = 0;
int c = 0;
boolean assigned = false;

Actions:

Thread A

v = 3;
assigned = true;

Thread B

while(!assigned);
c = v;

We have assigned without volatile for now.

The value of c in the Thread B can be equal 0 or 3 in such situation. So there is not any guaranties that c == 3.

v.ladynev
  • 19,275
  • 8
  • 46
  • 67