I'm having a little disagreement on Java's happens-before and synchronization.
Imagine the following scenario:
Main Thread
MyObject o = new MyObject(); // (0)
synchronized (sharedMonitor) {
// (1) add the object to a shared collection
}
// (2) spawn other threads
Other Threads
MyObject o;
synchronized (sharedMonitor) {
// (3) retrieve the previously added object
}
// (4) actions to modify the object
Note that the instance variables of MyObject
aren't neither volatile
, nor final
.
The methods of MyObject
do not use synchronization.
It is my understanding that:
1 happens-before 3, since there's synchronization on the same monitor, and the other threads are spawned only at 2, which is executed after 1.
Actions on 4 have no guarantees of being later visible to the main thread, unless there's further synchronization for all threads, and the main thread somehow synchronizes after these actions.
Q: Is there any guarantee of the actions at 0 being visible, happening-before, concurrent access on 3, or must I declare the variables as volatile
?
Consider now the following scenario:
Main Thread
MyObject o = new MyObject(); // (0)
synchronized (sharedMonitor) {
// (1) add the object to a shared collection
}
// (2) spawn other threads, and wait for their termination
// (5) access the data stored in my object.
Other Threads
MyObject o;
synchronized (sharedMonitor) {
// (3) retrieve the previously added object
}
o.lock(); // using ReentrantLock
try {
// (4) actions to modify the object
} finally { o.unlock(); }
It is my understanding that:
1 happens-before 3, just as before.
Actions on 4 are visible between the other threads, due to synchronization on the ReentrantLock
held by MyObject
.
Actions on 4 logically happen after 3, but there's no happens-before relation from 3 to 4, as consequence of synchronizing on a different monitor.
The point above would remain true, even if there was synchronization on sharedMonitor
after the unlock
of 4.
Actions on 4 do not happen-before the access on 5, even though the main thread awaits for the other tasks to terminate. This is due to the access on 5 not being synchronized with o.lock()
, and so the main thread may still see outdated data.
Q: Is my understanding correct?
Q: Is there any guarantee of the actions at 0 being visible, happening-before, concurrent access on 3, or must I declare the variables as volatile?
Yes there is a guarantee. You do not need the have the synchronized
block in the main thread because there is a happens-before relationship when the threads are started. From JLS 17.4.5: "A call to start() on a thread happens-before any actions in the started thread."
This also means that if you pass your o
into the thread constructor you wouldn't need the synchronized
block around (3) either.
Actions on (4) logically happen after (3), but there's no happens-before relation from (3) to (4), as consequence of synchronizing on a different monitor.
Yes and no. The logical order means that in the same thread there is certainly a happens-before relationship even though it is different monitor. The compiler is not able to reorder 3 past 4 even though they are dealing with different monitors. The same would be true with an access to a volatile
field.
With multiple threads, since (3) is only reading the object then there is not a race condition. However, if (3) was making modifications to the object (as opposed to just reading it), then in another thread those modifications may not be seen at (4). As you quote and @StephenC reiterates, the JLS says that the happens-before relationship is only guaranteed on the same monitor. JLS 17.4.5: "An unlock on a monitor happens-before every subsequent lock on that monitor."
The point above would remain true, even if there was synchronization on sharedMonitor after the unlock of (4).
See above.
Actions on (4) do not happen-before the access on (5), even though the main thread awaits for the other tasks to terminate
No. Once the main thread calls thread.join()
and it returns without getting interrupted then the main thread is synchronized fully with the memory of the thread it joined with. There is a happens-before relationship between the thread being joined with and the thread doing the joining. JLS 17.4.5: "All actions in a thread happen-before any other thread successfully returns from a join() on that thread."
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With