because (1) and (2) are executed in the same thread, one has hb(1,2) and analogue hb(3,4). In (2) is an unlock of the monitor and in (3) a lock of the same monitor, thus hb(2,3), therefore hb(1,4) and str should be equal to "newValue". Is that correct?
Yes, your logic is correct for this specific scenario. If (and only if) 2
executes before 3
then hb(2, 3)
. To understand why it should be, imagine a thread process such as the following:
localState *= 2;
synchronized(object) {
sharedState = localState;
}
Although localState
is computed outside a synchronized block, it should be necessary for other threads to see this computation to also see the correct value for sharedState
.
However, it's important to understand that there is no reason to expect the order you've asked about as the outcome. For example it could just as easily happen to execute this way:
(1)T1: data.setValue("newValue");
(3)T2: synchronized(object){}
(4)T2: String str=data.getValue();
(2)T1: synchronized(object){}
This is bad because now T1
is writing to a location in memory without synchronization while T2
is about to read it. (T2
could even read at the same time the write is occurring!)
To understand what happens-before is all about, instead imagine these threads are running concurrently (as threads do) and execute under the following timeline:
| T1 | T2
-------------------------------------------------------------
1 | synchronized(object){} |
2 | data.setValue("newValue"); | String str=data.getValue();
3 | | synchronized(object){}
Notice how I've aligned these hypothetical actions.
- At point
1
, T1
acquires the lock and releases it.
- At point
2
, T1
executes a write while simulaneously T2
executes a read.
- At point
3
, T2
acquires the lock and releases it.
But which actually happens first at point 2
? T1
's write or T2
's read?
Synchronization doesn't guarantee the order that threads actually execute with respect to one another. Instead, it is about memory consistency between threads.
At point 2
, because there is no synchronization, even if T1
actually makes the write before T2
reads it, T2
is free to see the old value in memory. Therefore it can appear that T2(2)
happened before T1(2)
.
Technically what this means is that outside of synchronization, a thread is free to read/write in a CPU cache instead of main memory. Synchronization forces the read/write in main memory.
Now with the second concurrent timeline:
T1 | T2
------------------------------------------------------------
synchronized(object){ | synchronized(object){
data.setValue("newValue"); | String str=data.getValue();
} | }
Although we do not have a guarantee about which thread acquires the lock first, we do have a guarantee that the memory access will be consistent. We also have a guarantee that their actions will not overlap, which was possible in the first timeline.
- If
T1
acquires the lock first, it is guaranteed that T1
's synchronized actions will appear as if happening before T2
's actions. (T1
will definitely write before T2
reads.)
- If
T2
acquires the lock first, it is guaranteed that T2
's synchronized actions will appear as if happening before T1
's actions. (T1
will definitely write after T2
reads.)