Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java's happens-before and synchronization

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?

like image 748
afsantos Avatar asked Jan 14 '23 02:01

afsantos


1 Answers

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."

like image 130
Gray Avatar answered Jan 19 '23 04:01

Gray