Synchronization of non-final field
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
A Java field does not need to be final to be thread-safe. What matters is whether updates and reads obey the Java Memory Model through synchronization, volatile, or another concurrency primitive. Problems start when code mixes protected and unprotected access and assumes visibility will somehow still work out.
final and Synchronization Are Different Guarantees
final helps with immutability and safe initialization. Synchronization is about visibility and coordinated access to mutable state.
A mutable field cannot be final by definition, but it can still be safe if all access is protected consistently.
This non-final field is safe because every access uses the same monitor.
The important point is not "is the field final." The important point is "how is the field published and accessed."
Why synchronized Works
Entering and leaving a synchronized block establishes a happens-before relationship. That means writes performed by one thread before releasing the lock become visible to another thread that later acquires the same lock.
Correct pattern:
Incorrect pattern:
- synchronize writes
- read without synchronization
That breaks the visibility guarantee, even if it "usually seems fine" on one machine.
When volatile Is Enough
If the field represents a simple value where each read and write is independent, volatile may be sufficient.
Here volatile ensures visibility. When one thread sets the flag, another thread will observe the change without explicit locking.
But volatile is not a general substitute for synchronization.
Compound Operations Need Stronger Coordination
This is unsafe:
count++ is not one indivisible action. It is:
- read the current value
- add one
- write the result
Two threads can interleave and lose updates.
Use synchronization or an atomic type instead:
Safe Publication Matters Too
Even if a field is read and written correctly later, the containing object still needs to be published safely to other threads.
For example, if one thread constructs an object and another thread reads a reference to it through an unsafely shared field, the second thread may observe stale or partially initialized state.
Safe publication can happen through:
- a properly synchronized handoff
- a
volatilereference - a thread-safe collection
- static initialization
Thread safety is not only about the field accessor methods. It is about the entire lifecycle of the object.
Choose the Simplest Correct Primitive
A practical strategy is:
- immutable object if possible
volatilefor simple visibility-only flags or referencessynchronizedfor coordinated mutable state- atomic classes for counters and compare-and-set style logic
Do not remove synchronization first and try to reason correctness back in later. Start with correctness, then optimize if measurement shows a real problem.
Common Pitfalls
- Assuming a non-final field is automatically unsafe even when every access is properly synchronized.
- Synchronizing writes but reading without the same lock.
- Using
volatilefor compound updates such as increment or check-then-act. - Ignoring safe publication of the containing object.
- Mixing several locking or visibility strategies for one field and losing a clear happens-before model.
Summary
- A non-final field can be thread-safe if it is accessed under the right memory-visibility rules.
- '
synchronizedprovides mutual exclusion and visibility when used consistently.' - '
volatileis useful for simple visibility cases, not for compound mutation logic.' - Safe publication matters just as much as safe access.
- Choose the smallest concurrency primitive that correctly matches the field's behavior.

