Two threads updating the same bank account balance. Two processes writing to the same file. Two servers modifying the same database row.
Without synchronization, these concurrent operations lead to corrupted data, lost updates, and bugs that are impossible to reproduce consistently.
Synchronization is the coordination of multiple threads to ensure correct behavior when accessing shared resources. It's one of the most important concepts in concurrent programming.
In this article, we'll explore:
If you're enjoying this newsletter and want to get even more value, consider becoming a [paid subscriber](https://blog.algomaster.io/subscribe).
As a paid subscriber, you'll unlock all premium articles and gain full access to all [premium courses](https://algomaster.io/newsletter/paid/resources) on [algomaster.io](https://algomaster.io).
When multiple threads access the same data and at least one of them modifies it, you have shared mutable state. This is where concurrency bugs originate.
Consider a simple counter:
This looks harmless. But count++ is not a single operation. It's actually three operations:
When two threads execute this "simultaneously," their operations can interleave in unexpected ways.
A race condition occurs when the correctness of a program depends on the relative timing of events in different threads.
Let's trace what happens when two threads increment our counter from 0:
Both threads read 0, both compute 1, both write 1. One increment is lost.
This is called a lost update problem. The final value depends on which thread writes last, not on the actual number of increments performed.
The result is unpredictable. You might get 987,432 one time and 991,876 another. This non-determinism makes race conditions extremely difficult to debug.
A critical section is a piece of code that accesses shared resources and must not be executed by more than one thread at a time.
In our counter example, the critical section is the count++ operation. To prevent race conditions, we need to ensure only one thread executes the critical section at a time.
This property is called mutual exclusion.
Mutual exclusion (mutex) ensures that only one thread can execute a critical section at any given time. Other threads must wait until the first thread exits the critical section.
The lock (also called mutex or monitor) is the mechanism that enforces mutual exclusion. A thread must acquire the lock before entering the critical section and release it when done.
Most languages provide built-in synchronization mechanisms. In Java, the synchronized keyword provides mutual exclusion.
When a thread calls a synchronized method:
this)Other threads calling synchronized methods on the same object must wait.
For finer control, you can synchronize specific blocks of code:
Synchronized blocks let you:
For more flexibility, most languages provide explicit lock classes. In Java, the ReentrantLock class offers additional features.
Synchronization provides three guarantees:
Only one thread can hold the lock at a time. This prevents race conditions in critical sections.
Changes made by one thread are visible to other threads. Without synchronization, threads might see stale values due to CPU caching.
When a thread releases a lock, all its writes are flushed to main memory. When another thread acquires the same lock, it sees those updates.
Operations before releasing a lock happen-before operations after acquiring the same lock. The compiler and CPU cannot reorder operations across lock boundaries in ways that would affect correctness.
Wait for a condition to become true:
Prevent deadlock by always acquiring locks in the same order:
Lazy initialization with minimal synchronization:
Note: The volatile keyword is essential here to prevent instruction reordering.
The volatile keyword provides visibility guarantees without mutual exclusion. It's useful for simple flags and single-variable state.
Important: volatile does NOT make count++ atomic. You still need synchronization for compound operations.
For simple atomic operations, use atomic classes instead of synchronization:
Atomic classes use hardware-level atomic instructions (like Compare-And-Swap) that are faster than locks.
Common atomic classes:
AtomicInteger, AtomicLong, AtomicBooleanAtomicReference<T>AtomicIntegerArray, AtomicLongArrayAll access to shared state must be synchronized, not just writes.
Always acquire multiple locks in a consistent order.
Synchronization coordinates thread access to shared resources, preventing race conditions and ensuring data consistency.
Key Concepts:
Best Practices:
volatile for simple flags, synchronized for compound operationsSynchronization is the foundation of correct concurrent programming. Master these basics before moving to advanced patterns like read-write locks, semaphores, and concurrent collections.
Thank you for reading!
If you found it valuable, hit a like and consider subscribing for more such content every week.
If you have any questions or suggestions, leave a comment.
This post is public so feel free to share it.
P.S. If you're enjoying this newsletter and want to get even more value, consider becoming a [paid subscriber](https://blog.algomaster.io/subscribe).
As a paid subscriber, you'll unlock all premium articles and gain full access to all [premium courses](https://algomaster.io/newsletter/paid/resources) on [algomaster.io](https://algomaster.io).
There are [group discounts](https://blog.algomaster.io/subscribe?group=true), [gift options](https://blog.algomaster.io/subscribe?gift=true), and [referral bonuses](https://blog.algomaster.io/leaderboard) available.
Checkout my [Youtube channel](https://www.youtube.com/@ashishps_1/videos) for more in-depth content.
Follow me on [LinkedIn](https://www.linkedin.com/in/ashishps1/) and [X](https://twitter.com/ashishps_1) to stay updated.
Checkout my [GitHub repositories](https://github.com/ashishps1) for free interview preparation resources.
I hope you have a lovely day!
See you soon, Ashish
[IMAGE 1: Race Condition Timeline] <!-- Shows two threads interleaving operations on shared variable -->
[IMAGE 2: Critical Section Diagram] <!-- Shows code flow with critical section highlighted -->
[IMAGE 3: Lock Acquisition Sequence] <!-- Shows threads acquiring and releasing locks -->
[IMAGE 4: Memory Visibility Problem] <!-- Shows CPU caches and main memory, stale data issue -->
[IMAGE 5: Synchronization Mechanisms Comparison] <!-- Table/diagram comparing synchronized, Lock, volatile, Atomic -->
[IMAGE 6: Decision Flowchart] <!-- Which synchronization approach to use -->