3

I have recently read http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html which clearly describes a lot of intrinsics of Java memory model. One particular excerpt got my attention namely:

The rule for a monitorexit (i.e., releasing synchronization) is that 
actions before the monitorexit must be performed before the monitor is released.

Seems obvious to me, however having read http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html and happens-before definition, all I could find about the monitor unblock is when one thread unlocks the monitor that happens-before the other thread locking it again (which makes perfect sense as well). Could someone explain how JLS explains the obvious condition that all the actions within the synchronization block must happen-before the unlock operation?

FURTHER COMMENTS:

Based on a couple of responses I wanted to write up further comments to what you guys have been saying:

  1. Reodering within a single-thread

A couple of "truths" from the source I cited:

a = new A()

if new A() involves a hundred operations, followed by assignment of address on a heap to a, compiler can simply reorder those to assign the heap address to a and then follow the usual initialization (problem from double checked locking)

synchronized{
    a = 5;
}
print a;

can be changed to

synchronized{
    a = 5;
    print a;
}

SO we reordered monitorexit with print statement (also valid according to JLS)

Now, the simple case I mentioned:

x = 1;
y = 2;
c = x + y;
print c;

I see no reason that stops a compiler from either assigning x first or y first. There is completely nothing stopping it, as the final output is unchanged regardless of whether x is assigned first or y is. So the reordering is perfectly possible.

  1. monitor.unlock

Based on the example with print statement being "pulled into" the synchronization block, let's try to reverse this i.e. startwing with

synchronized{
    a = 5;
    print a;
}

I could expect the compiler to do this:

synchronized{
    a = 5;
}
 print a;

Seems perfectly reasonable within the single-threaded world, YET this is definitely invalid, and against JLS (according to the cited source). Now why is that the case, if I cannot find anything within the JLS about this? and clearly the motivation about the "order of the program" is now irrelevant, since the compiler can make reoderings such as "pulling in" the statements to the synchronized block.

Bober02
  • 15,034
  • 31
  • 92
  • 178
  • I don't get the point of your concluding sentence: `and clearly the motivation about the "order of the program" is now irrelevant, since the compiler can make reoderings such as "pulling in" the statements to the synchronized block.` – Marko Topolnik Feb 18 '14 at 14:48
  • Program order is definitely important as it defines the *happens-before* relationship. Pulling a statement into the block doesn't violate the constraints; pushing them out does. It's rather obvious, actually. – Marko Topolnik Feb 18 '14 at 14:49
  • Sure, it is obvious, based on what definition in JLS? This is my question - everything must be documented formally otherwise it is just a belief that it should work this way. – Bober02 Feb 18 '14 at 15:10
  • Exactly, it is quite formally specified. The specification says that `print a` happens before lock release. My current guess is that you are misunderstanding something badly about the meaning of that sentence. – Marko Topolnik Feb 18 '14 at 15:21

2 Answers2

2

It's not just all actions performed within the synchronized block, it's also referring to all actions by that thread prior to the monitorexit.

Could someone explain how JLS explains the obvious condition that all the actions within the synchronization block must happen-before the unlock operation?

For a particular thread (and only one thread) all actions regardless of synchronized maintains program order, so it appears as if all reads and writes happen in order (we don't need a happens-before ordering in a single-thread case).

The happens-before relationship takes into account multiple threads, that is all actions happening in one thread prior to monitorexit are visible to all threads after a successive monitorenter.

EDIT to address your update.

There are particular rules the compiler must follow to re-order. The specific one in this case is demonstrated in the Can Reorder grid found here

Specifically useful entries are

  • First Action: Normal Load (load a; print a)
  • Second Action: Monitor Exit

The value here is No meaning the compiler cannot reorder two actions in which the first is a normal load and the second is monitorexit so in your case this reorder would violate the JLS.

There is a rule known as roach-motel ordering, that is read/writes can be reordered into a synchronized block but not out of it.

John Vint
  • 39,695
  • 7
  • 78
  • 108
  • 1
    I disagree with most of what you said. If I have a program `a=b; b=1; c = a + b; print c` Compiler is freely allowed to reoder the first two statements. Therefore, the actions before the monitor exit might be reordered, AND at the same time, some actions AFTER monitorexit might be reordered to happen before the monitorexit (to cite the source). But apparently, it is impossible for an action being before monitorexit to happen after that, which is precisely what I do not understand based on JLS – Bober02 Feb 18 '14 at 13:17
  • 1
    @Bober02 no, the compiler cannot reorder those statements as it would change the behaviour from `print initial_value_of_b + 1` to `print 2`. – Pete Kirkham Feb 18 '14 at 13:25
  • @Bober02 Pete is exactly right. In order for the JIT to re-order any statement it must first not effect the program itself (maintain program order). If a single thread running has different outputs from a reorder than the JIT is in violation. – John Vint Feb 18 '14 at 13:29
  • @Bober02 If you can illustrate your concerns more, can you include the code snippet in your question with respective monitorenter & monitorexit (or `synchronized`) – John Vint Feb 18 '14 at 13:32
  • @PeteKirkham I think you are missing Bober02's point... the actions may not be reordered causally, but they may be reordered temporally. If reordered temporally, then another thread, being able to observe the temporal ordering, will see actions performed out of program order. – Marko Topolnik Feb 18 '14 at 13:33
  • Please see further comments – Bober02 Feb 18 '14 at 14:21
  • @MarkoTopolnik the reordering of the effect of the operations as visible between threads is not the result of the compiler reordering the operations. The compiler cannot reorder those operations, but the result can become visible to another thread in a different order. For other operations where the compiler could reorder, memory fences prevent the compiler doing so, but the example is not such a case. The temporal order of the operations remains the same, unless you're running java in a time machine - its an effect of caching and when global memory is written to. – Pete Kirkham Feb 18 '14 at 16:40
  • @PeteKirkham Just visit my [recent question](http://stackoverflow.com/questions/21738690/strange-jit-pessimization-of-a-loop-idiom) here. You will witness first-hand machine code which is clearly performing operations out of order, and quite severely so: it has turned a loop such as `for (i = 0; i < 2; i++) sum +=array[i];` into `{ int a = array[i++], b = array[i++]; sum = sum + a + b; }`. Now assume `i` and `sum` are static variables, shared among threads. Can you spot the reordering here? – Marko Topolnik Feb 18 '14 at 17:26
  • @MarkoTopolnik Yes, the compiler can unwind loops and reorder where the behaviour of a single thread executing the code is unchanged. Bober02's suggested reordering would have changed the behaviour, so the compiler cannot do that. That doesn't mean it can't reorder some other code, as long as the behaviour of that code is unchanged when observed within that thread. In addition, the compiler cannot reorder across fences (monitor acquire and release) but it's up to the programmer to create a system that co-operating threads use those fences to ensure consistency. – Pete Kirkham Feb 18 '14 at 18:11
  • @PeteKirkham I see, you retract your previous statement, then? That `the reordering of the effect of the operations as visible between threads is not the result of the compiler reordering the operations.`----because that statement is patently false. – Marko Topolnik Feb 18 '14 at 19:05
  • @MarkoTopolnik no, my earlier statement stands. Visibility of the operations between threads is primarily an effect of cache and memory subsystems, and will occur even if you wrote the program in machine code. It is not a function of the compiler - specifically compilers are limited so that they cannot reorder operations so the observed effect changes within the executing thread. If the architecture was such all writes were synchronized back to main memory in order of execution, then that would cause the effect observed by any other thread to match the effect observed by the executing thread.. – Pete Kirkham Feb 19 '14 at 12:37
  • ... because the compiler can't reorder in a way that changes the observed behaviour in the executing thread, and in such a machine all threads see all writes immediately. Therefore compiler reordering does not have any effect on the visibility of state between threads, only the write back behaviour of the architecture. I would suggest if you don't understand this, ask another question rather than continuing a comment discussion. – Pete Kirkham Feb 19 '14 at 12:38
  • @PeteKirkham I suggest you ask the question, in an attempt to gain proper understanding of this issue. The compiler reordering can *by itself* cause out-of-order observation for other threads. Note also that Intel's CPUs already enforce the ordering of writes. – Marko Topolnik Feb 19 '14 at 12:41
  • @PeteKirkham Specifically, your thinking is wrong here: `then that would cause the effect observed by any other thread to match the effect observed by the executing thread.` The executing thread must only maintain the invariants imposed by the source code it is executing; it has no obligation to preserve order at a lower level than that. The compiler reorders with that in mind. Another thread is not aware of this and can perceive the actions taking place out-of-order in a way which breaks the high-level invariants. They are not on an equal footing in this respect. – Marko Topolnik Feb 19 '14 at 12:48
  • not sure why this didn't link http://chat.stackoverflow.com/rooms/47857/discussion-between-pete-kirkham-and-marko-topolnik – Pete Kirkham Feb 19 '14 at 13:03
1

Maybe you missed this (§17.4.5):

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

Combined with what you already know about happens-before, it should be clear that this implies that all actions preceding the unlock action will be visible to the other thread.

Regarding your additions to the question, if you write this:

synchronized {
    a = 5;
    b = 3;
}

and the compiler emits this:

synchronized{
    a = 5;
}
b = 3;

then the stipulation I have quoted above is violated: now b = 3 does not happen before the lock-release. This is why it is illegal. (Note that your example with print a isn't instructive because it involves only reading + side effects not easily describable with simple variables.)

Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
  • I tried to explain why this does not convince me in further comments to the initial questions – Bober02 Feb 18 '14 at 14:22
  • In your addition, the reordering is illegal exactly because it violates the stipulation I have quoted. – Marko Topolnik Feb 18 '14 at 14:28
  • your stipulation says `If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).` NOw, in my example, it is apparent that `a=1; b=1` is equivalent to `b=1; a=1` so the reordering is possible. Back to the stipulation - `a=1` is a normal write, so is `b=1`. SO it seems I can reorder actions within the same thread. Now, why can I reorder those, and not monitor.unlock and a normal write? – Bober02 Feb 18 '14 at 15:31
  • You can reorder them *temporally*, but not *causally*. In other words, the reordering is not perceivable by the current thread. You can reorder those because there is no hb-edge going from either of them directly to an action by another thread. They both, as a unit, happen before the monitor-release action, which happens before the monitor acquire action. – Marko Topolnik Feb 18 '14 at 15:55