AlgoMaster Logo

Synchronization Basics

14 min readUpdated December 16, 2025
Listen to this chapter
Unlock Audio

Synchronization Basics

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:

  • Why synchronization is necessary
  • Race conditions and data races
  • Critical sections
  • Mutual exclusion and locks
  • Synchronization mechanisms
  • Common patterns and pitfalls

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).

Unlock Full Access

1. The Problem: Shared Mutable State

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:

  1. Read the current value of count
  2. Add 1 to that value
  3. Write the new value back to count

When two threads execute this "simultaneously," their operations can interleave in unexpected ways.

2. Race Conditions

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.

Demonstrating the Race Condition

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.

3. Critical Sections

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.

4. 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.

5. The synchronized Keyword

Most languages provide built-in synchronization mechanisms. In Java, the synchronized keyword provides mutual exclusion.

Synchronized Methods

When a thread calls a synchronized method:

  1. It acquires the lock on the object (this)
  2. Executes the method
  3. Releases the lock (even if an exception occurs)

Other threads calling synchronized methods on the same object must wait.

Synchronized Blocks

For finer control, you can synchronize specific blocks of code:

Synchronized blocks let you:

  • Protect only the critical section (better performance)
  • Use different locks for different resources
  • Use any object as a lock

6. Lock Objects

For more flexibility, most languages provide explicit lock classes. In Java, the ReentrantLock class offers additional features.

synchronized vs Lock Objects

Scroll
Feature
synchronized
Lock (ReentrantLock)

Syntax

Built-in keyword

Explicit API

Release

Automatic

Manual (must call unlock)

Try to acquire

No

Yes (tryLock)

Timeout

No

Yes (tryLock with timeout)

Interruptible

No

Yes (lockInterruptibly)

Fairness

No

Optional (fair lock)

Multiple conditions

No

Yes (multiple Condition objects)

Try Lock (Non-blocking)

Lock with Timeout

7. What Synchronization Guarantees

Synchronization provides three guarantees:

7.1 Mutual Exclusion

Only one thread can hold the lock at a time. This prevents race conditions in critical sections.

7.2 Visibility

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.

7.3 Ordering

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.

8. Common Synchronization Patterns

Pattern 1: Guarded Blocks

Wait for a condition to become true:

Pattern 2: Lock Ordering

Prevent deadlock by always acquiring locks in the same order:

Pattern 3: Double-Checked Locking

Lazy initialization with minimal synchronization:

Note: The volatile keyword is essential here to prevent instruction reordering.

9. The volatile Keyword

The volatile keyword provides visibility guarantees without mutual exclusion. It's useful for simple flags and single-variable state.

volatile vs synchronized

Scroll
Aspect
volatile
synchronized

Mutual exclusion

No

Yes

Visibility

Yes

Yes

Atomicity

Only for reads/writes

Yes (for entire block)

Use case

Flags, single variables

Compound operations

Performance

Faster

Slower

Important: volatile does NOT make count++ atomic. You still need synchronization for compound operations.

10. Atomic Variables

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, AtomicBoolean
  • AtomicReference<T>
  • AtomicIntegerArray, AtomicLongArray

11. Common Pitfalls

Pitfall 1: Synchronizing on Wrong Object

Pitfall 2: Holding Locks Too Long

Pitfall 3: Inconsistent Locking

All access to shared state must be synchronized, not just writes.

Pitfall 4: Deadlock from Lock Ordering

Always acquire multiple locks in a consistent order.

Pitfall 5: Using notify() Instead of notifyAll()

12. Choosing the Right Approach

Scenario

Counter, simple numeric ops

AtomicInteger

Boolean flag, status

volatile

Compound operations

synchronized

Need timeout/tryLock

ReentrantLock

Multiple wait conditions

Lock + Condition

High read, low write

ReadWriteLock

13. Summary

Synchronization coordinates thread access to shared resources, preventing race conditions and ensuring data consistency.

Key Concepts:

Concept
Description

Race Condition

Bug where outcome depends on thread timing

Critical Section

Code accessing shared resources

Mutual Exclusion

Only one thread in critical section at a time

Lock/Monitor

Mechanism enforcing mutual exclusion

Visibility

Ensuring threads see latest values

Best Practices:

  1. Minimize shared mutable state
  2. Keep critical sections short
  3. Always release locks (use try-finally)
  4. Use consistent lock ordering
  5. Prefer higher-level concurrency utilities
  6. Use volatile for simple flags, synchronized for compound operations
  7. Consider atomic classes for simple counters

Synchronization 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.

Share

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).

Unlock Full Access

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

References

Images Needed

[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 -->