I read this stackoverflow topic, which conclusion seems to be that an empty synchronized block can always be avoided with a nicer solution. The topic also has some unclear parts to me, which I’ll integrate to my below post.
Assume we have a class like this:
public class MyThreadClass extends Thread {
private final OtherComponent mOtherComponent;
private final Object mLock = new Object();
private MyHandler mHandler;
public MyCustomThread(OtherComponent otherComponent) {
mOtherComponent = otherComponent;
public void run() {
mHandler = new Handler() {...}
// Other init operations
mOtherComponent.onMyCustomThreadInitialized();
// ... other operations (Looper.loop(), actually)
}
public void sendMessageWithHandler(int what, int arg) {
synchronized (mLock) {}
Message msg = mHandler.obtainMessage(what);
msg.arg1 = arg;
mHandler.sendMessage(msg);
}
public void useHandlerInAnotherWay(...) {
synchronized (mLock) {
// useful code that needs actual mutual exclusion
}
mHandler.sendMessage(...);
}
}
The relevant part of my application works in the following way:
- The
MyThreadClassthread is created and started. - As an indirect consequence of
mOtherComponent.onMyCustomThreadInitialized(), another part of my application will start spawning other threads. (Note that they aren’t started synchronously from this call, that is why I say it’s indirect consequence. The only point is,mHandlerhas been initialized by the time these other threads are started) - Each of the other threads will call
sendMessageWithHandler(...)exactly once - Yet again other threads (i.e. not the threads mentioned above) call
useHandlerInAnotherWay(...), this can happen at any time (aftermOtherComponent.onMyCustomThreadInitialized(), of course).
My questions:
-
If I’m correct, up-to-date data visibility must be guaranteed when
mHandleris accessed from other threads thanmyThreadClass, because it is not afinalfield. I don’t want to make itvolatileeither, because except for these fewsendMessageWithHandler(..)calls,mHandleris not used from other threads without synchronization (I don’t want thevolatileoverhead to be present unnecessarily where it’s needless). In other words, whenmHandleris accessed from those yet other threads viauseHandlerInAnotherWay(), thesynchronizedthere with the “useful code” (i.e. code that actually needs to be a subject of mutual exclusion) also guarantees that the caller thread seesmHandlercorrectly. InsendMessageWithHandler(..), however, the code does not require mutual exclusion, so I decided to put an empty synchronized block to the beginning ofsendMessageWithHandler(...). Is this correct? Is there a nicer solution to my problem? -
The other stackoverflow thread I linked to has the following answer (it’s not the accepted one, but was upvoted multiple times):
It used to be the case that the specification implied certain memory
barrier operations occurred. However, the spec has now changed and the
original spec was never implemented correctly. It may be used to wait
for another thread to release the lock, but coordinating that the
other thread has already acquired the lock would be tricky.Does this mean that the empty
synchronizeddoes not offer the memory barrier functionality anymore? If I check the Java docs online aboutsynchronized, they mention that ALL memory is updated due to it (i.e. thread copies are updated from “main memory” at monitor enter, and “main memory” is updated from thread copies at monitor exit). But they don’t mention anything about emptysynchronizedblocks, so this is unclear to me.
You probably don’t need any synchronization.
readis guaranteed to seewrite.The semantics of
synchronizedis strictly enforced, even if it’s an empty block.