Using synchronized Keyword for Thread Safety in Java – Explained with Examples

Illustration for Using synchronized Keyword for Thread Safety in Java – Explained with Examples
By Last updated:

Thread safety is critical in multithreaded Java programs. Without proper synchronization, shared data can lead to unpredictable behavior, bugs, and crashes. One of the most fundamental ways Java addresses this problem is through the synchronized keyword.

In this guide, we’ll explore how synchronized works, when to use it, and how it compares to other locking mechanisms like ReentrantLock.


What Is Thread Safety?

Thread safety means ensuring that shared data is accessed and modified in a predictable and safe manner across multiple threads. Without thread safety:

  • Data races can occur
  • Inconsistent views of memory can arise
  • Application behavior becomes unreliable

What Does synchronized Do?

The synchronized keyword:

  • Prevents multiple threads from entering a critical section simultaneously
  • Provides memory visibility (flushes variables from thread-local caches)

Syntax of synchronized

Synchronized Method

public synchronized void increment() {
    counter++;
}

Synchronized Block

public void increment() {
    synchronized (this) {
        counter++;
    }
}

Synchronized on Class Object

synchronized (MyClass.class) {
    // static-level locking
}

Real-World Analogy

Imagine a bank teller counter. Only one customer can access the teller at a time — others must wait. The synchronized keyword enforces the same rule for threads.


Thread Lifecycle Impact

synchronized causes threads to enter the BLOCKED state if the monitor (lock) is already held.

Lifecycle path:

NEW → RUNNABLE → BLOCKED → RUNNING → TERMINATED

Java Memory Model & Visibility

  • synchronized ensures happens-before relationship
  • Writes before unlocking are visible to reads after locking
  • Prevents stale data and race conditions

Common Use Cases

  • Counters and shared counters
  • Updating shared collections
  • Ensuring one-at-a-time access to sensitive code

Comparison with ReentrantLock

Feature synchronized ReentrantLock
Simplicity Easy to use More verbose
Locking granularity Method/block only Full control
Interruptible No Yes
Timeout support No Yes
Fairness No Yes (optional)

Best Practices

  • Keep synchronized blocks short
  • Lock on private final objects if possible
  • Avoid nested locks (can cause deadlocks)
  • Prefer block over method-level locking for flexibility

Anti-Patterns and Pitfalls

  • Locking on this exposes lock to external code
  • Using string literals for locking
  • Holding locks during I/O or long computation
  • Over-synchronization can cause performance bottlenecks

Java Version Tracker

📌 What's New in Java Versions?

Java 8

  • Lambdas for clean thread syntax
  • ConcurrentHashMap improvements

Java 9

  • Cleaner stack inspection APIs

Java 11+

  • var support for more readable code

Java 21

  • Virtual threads can use synchronized
  • Structured concurrency encourages scoped locking
  • Scoped values for contextual thread data

Real-World Scenarios

  • Inventory counters
  • Banking transactions
  • Thread-safe caching layers
  • Atomic logging to shared file or output

Thread Coordination with synchronized

synchronized works well with:

  • wait() / notify() / notifyAll()
  • They must be used inside synchronized blocks
synchronized (lock) {
    lock.wait(); // Releases lock temporarily
}

Design Patterns Using synchronized

  • Monitor Object Pattern
  • Producer-Consumer
  • Thread-per-Message
  • Guarded Suspension

Conclusion and Key Takeaways

  • synchronized is the simplest way to protect critical sections
  • It ensures both mutual exclusion and memory visibility
  • Proper usage prevents race conditions and maintains thread safety
  • For advanced control, consider ReentrantLock or java.util.concurrent tools

FAQs

Q1: Does synchronized block only one thread?
A: Yes, only one thread can hold the lock at a time.

Q2: Can static methods be synchronized?
A: Yes. It locks on the class object, not instance.

Q3: What happens if a thread holding a lock throws an exception?
A: The lock is automatically released.

Q4: Does synchronized work with virtual threads?
A: Yes, fully supported as of Java 21.

Q5: Can I lock on null objects?
A: No. It throws NullPointerException.

Q6: Can a thread enter multiple synchronized blocks?
A: Yes, if they are synchronized on different objects.

Q7: How is synchronized different from volatile?
A: synchronized ensures atomicity + visibility, volatile ensures visibility only.

Q8: Can I use synchronized with arrays or collections?
A: Yes, but consider using concurrent collections for scalability.

Q9: What tool can help detect synchronization issues?
A: VisualVM, JFR, or profilers like YourKit.

Q10: Should I use synchronized on a getter?
A: Only if the getter accesses shared mutable data.