How to synchronize a static variable among threads running different instances of a class in Java?
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
In Java, static variables are shared among all instances of a class, as they belong to the class itself rather than any particular instance. This can present concurrency challenges when multiple threads from different instances attempt to modify a static variable at the same time. Proper synchronization is required to ensure thread safety and maintain the integrity of shared data.
Thread Safety with Static Variables
Why Synchronization?
Without synchronization, multiple threads can simultaneously read and write to a static variable, leading to race conditions, which can corrupt the data. Synchronization ensures that only one thread can modify the variable at any given time, maintaining data consistency across threads.
The Role of the synchronized Keyword
In Java, the synchronized keyword can be applied to a method or a block of code to restrict its access to a single thread at a time. When synchronizing a static method, the lock is on the class object (Class), not on the instance, since the static method can be accessed without an instance.
Synchronizing Static Variables
Consider an example class that incrementally updates a static counter, demonstrating how to synchronize access to this shared resource:
In this example, the increment() method is declared as synchronized, ensuring that count is thread-safe. An alternative is to use a synchronized block with the class as a lock, which allows more granular control over synchronization scope within a method.
Choosing Between Synchronized Methods and Blocks
- Synchronized Methods: Easier to use for simple cases where the entire method needs synchronization. However, it locks the entire method, which can lead to lower concurrency.
- Synchronized Blocks: Allow synchronization of only the critical section of code that modifies the shared resource. This can provide better performance for more complex methods by allowing non-critical sections to run concurrently.
Ensuring Visibility with volatile
While synchronization ensures atomic operations, it does not automatically ensure visibility of changes to variables across threads. This is where the volatile keyword can be useful. A volatile variable guarantees visibility by ensuring changes to the variable are immediately reflected in the main memory, and any thread reading the variable will see the most recent value.
Comparing volatile with Synchronization
| Aspect | volatile | synchronized |
| Atomicity | Does not guarantee atomicity | Ensures atomic operations |
| Visibility | Ensures visibility | Ensures synchronization visibility |
| Performance | Faster due to no locking overhead | Slower due to lock acquisition and release overhead |
| Use Case | Flags, states (simple read/write) | Complex operations requiring atomicity (like multiple read-modify-write) |
Best Practices for Synchronizing Static Variables
- Minimize Use of Static Variables: Whenever possible, avoid using static variables for shared data to limit concurrency issues.
- Use Proper Locking: Only lock the necessary code segment to enhance performance.
- Monitor Performance: Too much synchronization can lead to bottlenecks. Profiling can help identify performance-critical sections.
- Consider Higher-level Concurrency Utilities: Java’s
java.util.concurrentpackage provides atomic classes, locks, and other utilities that can simplify managing concurrency.
Example with AtomicInteger
Instead of synchronizing access manually, the AtomicInteger class from java.util.concurrent.atomic can provide an out-of-the-box solution for thread-safe operations:
Advantages of Using AtomicInteger:
- Atomic operations for common use-cases
- Reduced synchronization overhead
- Simpler and clearer code
Conclusion
Synchronizing static variables across different threads is an essential practice in Java to prevent race conditions and ensure data integrity. By understanding the roles of synchronized, volatile, and advanced concurrency utilities like AtomicInteger, developers can build thread-safe applications. Proper application of these techniques can mitigate concurrency problems and enhance the reliability and performance of Java applications.
Related reading
- How to synchronize data between two tables in different databases
- How to synchronize distributed system data across cassandra clusters
- How to terminate a thread blocking on socket IO operation instantly?
- How to terminate a thread in C?
- How to tell a Mockito mock object to return something different the next time it is called?
- How to tell Jackson to ignore a field during serialization if its value is null?
- How to terminate a thread when main program ends?
- How to test an asynchronous JavaScript function Promises, Jasmine, PhantomJS

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.