Basically there is no difference between the presence and absence of the synchronized
block. However, I can think of a situation that could give other meaning to this usage.
The synchronized
blocks has an interesting side-effect of causing a memory barrier to be created by the runtime before entering and after leaving the block. A memory barrier is a special instruction to the CPU that enforces all variables that are shared between multiple threads to return their latest values. Usually, a thread works with its own copy of a shared variable, and its value is visible to this thread only. A memory barrier instructs the thread to update the value in a way so that the change is visible to the other threads.
So, the synchronized block in this case does not do any locking (as there will be no real case of lock and wait situation, at lest none I can think of)(unless the use-case mentioned in this answer is addressed), but instead it enforces the values of the shared fields to return their latest value. This, however, is true if the other places of the code that work with the variables in question also uses memory barriers (like having the same synchronized
block around the update/reassignment operations). Still, this is not a solution for avoiding race conditions.
If you're interested, I recommend you to read this article. It is about memory barriers and locking in C# and the .NET framework, but the problem is similar for Java and the JVM (except for the behavior of volatile fields). It helped me a lot in understanding how threads, volatile fields and locks work in general.
One must take into account some serious considerations in this approach, that were mentioned in comments below this answer.
- The memory barrier does not imply locking. The access will still be non-synchronized and a subject to race conditions and other potential issues one may encounter. The only benefit is the thread being able to read the latest values of the shared memory fields, without the use of locks. Some practices use similar approaches if the working thread only reads from values and it does only care for them to be the most present ones, while avoiding the overhead of locks - a use case could be a high-performance simultaneous data processing algorithm.
- The approach above is unreliable. As per Holger's comment, the compiler could eliminate the lock statements when optimizing, as it could consider them unnecessary. This will also remove the memory barriers. The code then will not issue a lock, and it will not work as expected if a lock was meant to be used, or the purpose was to create a memory barrier.
- The approach above is also unreliable because the runtime JVM can remove synchronization when it can prove the monitor will never be acquired by another thread, which is true of this construct if the code never synchronizes on another thread object which is not the current thread's thread object. So even if it works during testing on system A, it might fail under another JVM on system B. Even worse, the code could work for a while and then cease working as optimizations are applied.
- The intentions of the code as it stays now are ambiguous, so one should use more explicit and expressive means to achieve its effect (see Marko Topolnik's comment for reference).