CodeToConcept
About

java

java Tutorials

Introduction
Java Variables
Java Data Types
Java Type Casting
Java Operators
Java Input/Output
Java Class & Object
java Method
Java Constructor
Java this keyword
Java super keyword
Java Method Overloading and Overriding
Java Interface
Java Abstract Class
Java Memory Management
Design Patterns
JDBC (Database Connectivity)
Java IDEs

java Tutorials

Introduction
Java Variables
Java Data Types
Java Type Casting
Java Operators
Java Input/Output
Java Class & Object
java Method
Java Constructor
Java this keyword
Java super keyword
Java Method Overloading and Overriding
Java Interface
Java Abstract Class
Java Memory Management
Design Patterns
JDBC (Database Connectivity)
Java IDEs

Sample Question

Think it through, then check the tutorial.

CodeToConcept

Quickly learn through simple, structured, and practical coding tutorials. Build your confidence from step zero to real-world experience.

© 2026 CodeToConcept. All rights reserved.

Quick Links

AboutTutorials

Tutorials

JavaHTMLCSS

Support

Privacy PolicyTerms of ServiceContact
GitHubTwitterYouTube

Java Synchronization

Two threads can overwrite each other's work when they update shared data without coordination. Synchronization lets them follow the same locking rule around the relevant work. The rule must cover the shared state, not just whichever method is easiest to mark synchronized.

What is Synchronization?

Synchronization is a mechanism that controls how multiple threads access shared resources.

When a resource is shared between multiple threads, synchronization can ensure that only one thread at a time executes a particular critical section.

For example, suppose two threads are trying to update the same bank account balance.

Without proper synchronization:

Thread 1 → reads balance
Thread 2 → reads balance
Thread 1 → updates balance
Thread 2 → updates balance

One update may overwrite another.

With synchronization:

Thread 1 → accesses resource
              ↓
           completes
              ↓
Thread 2 → accesses resource

This helps prevent inconsistent results.

Why is Synchronization Needed?

Synchronization is mainly needed when multiple threads access shared mutable data.

Consider:

class Counter {
 
    int count = 0;
 
    void increment() {
        count++;
    }
}

Now suppose two threads execute:

counter.increment();

at the same time.

The statement:

count++;

is not a single indivisible operation. Conceptually, it involves:

Read count
   ↓
Add 1
   ↓
Write count

Two threads can interfere with these steps.

This can produce an incorrect final value.

What is a Race Condition?

A race condition occurs when the result of a program depends on the timing or order in which multiple threads access shared data.

Example

class Counter {
 
    int count = 0;
 
    void increment() {
        count++;
    }
}

Suppose the initial value is:

count = 0

Two threads execute increment() simultaneously.

A possible sequence is:

Thread-1 → reads 0
Thread-2 → reads 0
Thread-1 → calculates 1
Thread-2 → calculates 1
Thread-1 → writes 1
Thread-2 → writes 1

Expected result:

2

Actual result:

1

This is a race condition.

Synchronization can prevent both threads from entering the critical section at the same time.

Synchronized Keyword

Java provides the synchronized keyword to implement synchronization.

It can be used with:

  • Instance methods
  • Static methods
  • Code blocks

For example:

synchronized void increment() {
    count++;
}

The synchronized keyword ensures that only one thread at a time can execute the synchronized instance method for the same object.

Synchronized Instance Method

A synchronized instance method locks the monitor associated with the object on which the method is invoked.

Example

class Counter {
 
    private int count = 0;
 
    public synchronized void increment() {
        count++;
    }
 
    public int getCount() {
        return count;
    }
}

Now multiple threads can safely call:

counter.increment();

for the same Counter object.

Only one thread at a time can execute the synchronized increment() method on that object.

How Synchronized Instance Method Works

Suppose:

Counter counter = new Counter();

Two threads call:

counter.increment();

The monitor associated with counter controls access.

Conceptually:

Thread-1
   ↓
acquires counter's monitor
   ↓
executes increment()
   ↓
releases monitor
 
Thread-2
   ↓
acquires counter's monitor
   ↓
executes increment()

The second thread must wait until the first thread releases the monitor.

Synchronized Block

Instead of synchronizing an entire method, we can synchronize only a specific section of code.

Syntax:

synchronized (object) {
    // critical section
}

Example

class Counter {
 
    private int count = 0;
 
    public void increment() {
 
        synchronized (this) {
            count++;
        }
    }
}

Here, only the code inside the synchronized block requires the monitor.

Why Use a Synchronized Block?

Synchronizing an entire method can sometimes lock more code than necessary.

Consider:

public synchronized void process() {
 
    // Code that does not access shared data
 
    // Code that accesses shared data
 
    // More code that does not access shared data
}

The entire method is protected by synchronization.

Instead, we can synchronize only the critical section:

public void process() {
 
    // Code that does not need synchronization
 
    synchronized (this) {
        // Critical section
    }
 
    // More code that does not need synchronization
}

This can reduce the amount of time a lock is held.

Synchronized Static Method

A static synchronized method locks the monitor associated with the class object, not an individual instance.

Example

class Counter {
 
    private static int count = 0;
 
    public static synchronized void increment() {
        count++;
    }
}

The lock is associated with:

Counter.class

Conceptually:

static synchronized method
          ↓
     Counter.class
          ↓
       monitor

This is useful when multiple threads access shared static data.

Synchronized Static Block

We can also synchronize a block using a class object.

class Counter {
 
    private static int count = 0;
 
    public static void increment() {
 
        synchronized (Counter.class) {
            count++;
        }
    }
}

Here, Counter.class is used as the monitor.

Instance Synchronization vs Static Synchronization

Instance SynchronizationStatic Synchronization
Locks an object monitorLocks the class object's monitor
Used with instance methods/blocksUsed with static methods/blocks
Lock associated with this for a synchronized instance methodLock associated with ClassName.class
Protects data associated with an objectCommonly protects shared static data

For example:

public synchronized void method() {
}

is conceptually similar to:

public void method() {
    synchronized (this) {
    }
}

Whereas:

public static synchronized void method() {
}

is conceptually similar to:

public static void method() {
    synchronized (ClassName.class) {
    }
}

What is a Monitor?

A monitor is the synchronization mechanism associated with an object.

Every Java object can be used as a monitor for synchronized operations.

For example:

synchronized (obj) {
    // critical section
}

Here, obj is used as the monitor.

A thread must acquire the monitor before entering the synchronized block.

When the thread exits the synchronized block, the monitor is released.

Critical Section

A critical section is a section of code that accesses shared data and must be executed with appropriate synchronization when concurrent access could cause incorrect results.

Example

synchronized (this) {
    count++;
}

Here:

synchronized block
       ↓
critical section
       ↓
shared data is accessed

The goal is to prevent unsafe concurrent access to the shared state.

Synchronization and Mutual Exclusion

Synchronization provides mutual exclusion for synchronized sections protected by the same monitor.

Mutual exclusion means:

At most one thread can hold a particular monitor at a time.

For example:

synchronized (lock) {
    // critical section
}

If Thread-1 owns the lock monitor, another thread cannot enter another synchronized block protected by the same lock until the monitor becomes available.

Using a Dedicated Lock Object

Instead of using this, we can create a dedicated object for synchronization.

Example

class Counter {
 
    private int count = 0;
 
    private final Object lock = new Object();
 
    public void increment() {
 
        synchronized (lock) {
            count++;
        }
    }
}

This can make the synchronization boundary more explicit.

It also avoids exposing the object's monitor as the lock used for synchronization.

Same Lock vs Different Locks

Synchronization works only when threads coordinate using the same monitor.

For example:

Object lock = new Object();
 
synchronized (lock) {
    // protected section
}

Another thread using the same lock object must wait if the first thread owns the monitor.

But different objects represent different monitors:

Object lock1 = new Object();
Object lock2 = new Object();

These are separate locks.

lock1 → Monitor 1
lock2 → Monitor 2

A thread holding lock1 does not prevent another thread from acquiring lock2.

Example: Thread-Safe Counter

A common example of synchronization is a counter shared between multiple threads.

Without Synchronization

class Counter {
 
    private int count = 0;
 
    public void increment() {
        count++;
    }
 
    public int getCount() {
        return count;
    }
}

Multiple threads can execute increment() concurrently.

This can cause a race condition.

With Synchronization

class Counter {
 
    private int count = 0;
 
    public synchronized void increment() {
        count++;
    }
 
    public int getCount() {
        return count;
    }
}

Now only one thread at a time can execute the synchronized increment() method for the same Counter object.

Complete Example

class Counter {
 
    private int count = 0;
 
    public synchronized void increment() {
        count++;
    }
 
    public int getCount() {
        return count;
    }
}
 
public class HelloWorld {
 
    public static void main(String[] args)
            throws InterruptedException {
 
        Counter counter = new Counter();
 
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
 
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
 
        thread1.start();
        thread2.start();
 
        thread1.join();
        thread2.join();
 
        System.out.println(counter.getCount());
    }
}

Output

2000

Because increment() is synchronized, the updates are protected by the same object's monitor.

Synchronization Does Not Make Every Operation Thread-Safe

Adding synchronized to one method does not automatically make an entire class thread-safe.

For example:

class Counter {
 
    private int count;
 
    public synchronized void increment() {
        count++;
    }
 
    public int getCount() {
        return count;
    }
}

The increment() method is protected, but thread safety depends on how the object is accessed and whether all relevant shared state is properly coordinated.

Synchronization must be designed around the actual shared state and operations.

Synchronized Methods on the Same Object

Consider:

class Test {
 
    public synchronized void method1() {
        // ...
    }
 
    public synchronized void method2() {
        // ...
    }
}

Both methods use the same instance monitor when called on the same object.

Therefore, two threads cannot simultaneously execute these methods on the same instance.

For example:

Test obj = new Test();

If Thread-1 is executing:

obj.method1();

Thread-2 cannot simultaneously enter:

obj.method2();

because both methods require the same object's monitor.

Synchronized Methods on Different Objects

Consider:

Test obj1 = new Test();
Test obj2 = new Test();

If one thread executes:

obj1.method1();

and another executes:

obj2.method1();

the two methods use different object monitors.

Therefore, synchronization on obj1 does not block synchronization on obj2.

Conceptually:

obj1 → Monitor 1
obj2 → Monitor 2

Synchronization and wait(), notify(), notifyAll()

Synchronization is closely related to the methods:

  • wait()
  • notify()
  • notifyAll()

These methods belong to Object.

They are used for thread coordination and must be called while the current thread owns the relevant object's monitor.

Example

synchronized (lock) {
    lock.wait();
}

Another thread can notify:

synchronized (lock) {
    lock.notify();
}

The relationship can be summarized as:

synchronized
     ↓
acquire monitor
     ↓
wait()
     ↓
release monitor and wait
     ↓
notify()
     ↓
thread can compete to reacquire monitor

The detailed behavior of these methods is part of Java's inter-thread communication mechanism.

Does sleep() Release the Lock?

No. This is an important distinction.

If a thread owns a monitor and calls:

Thread.sleep(2000);

it enters TIMED_WAITING, but it does not release the monitor because of sleep().

Example

synchronized (lock) {
 
    Thread.sleep(2000);
 
}

The thread continues to hold the monitor while sleeping.

By contrast, wait() releases the monitor of the object on which wait() was called.

Synchronization and Performance

Synchronization introduces coordination between threads and can reduce concurrency when used unnecessarily.

For example:

public synchronized void process() {
 
    // Large amount of code
}

The entire method requires the object's monitor.

If only a small portion needs protection, a synchronized block may be more appropriate:

public void process() {
 
    // Non-critical work
 
    synchronized (lock) {
        // Critical section
    }
 
    // More non-critical work
}

The goal is not to avoid synchronization completely, but to synchronize the shared state correctly and keep the protected region appropriately scoped.

Synchronization and Deadlock

Poorly designed synchronization can lead to a deadlock.

A deadlock can occur when two or more threads wait indefinitely for locks held by each other.

Example concept:

Thread-1
   ↓
holds Lock-A
   ↓
waits for Lock-B
 
Thread-2
   ↓
holds Lock-B
   ↓
waits for Lock-A

Neither thread can continue.

Lock-A ← Thread-1
   ↑
   |
Thread-2
   |
   ↓
Lock-B

Careful lock ordering and minimizing unnecessary nested locking can help avoid deadlocks.

Synchronization vs volatile

volatile and synchronized solve different problems.

volatile

A volatile variable provides visibility guarantees for reads and writes of that variable between threads.

private volatile boolean running = true;

However, volatile does not make compound operations such as:

count++;

atomic.

synchronized

synchronized provides mutual exclusion and also establishes the required memory visibility guarantees around monitor locking and unlocking.

For example:

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

So:

volatile     → visibility of a variable
synchronized → mutual exclusion + visibility guarantees

The correct choice depends on the problem being solved.

Synchronization vs Atomic Classes

For simple atomic updates, Java also provides classes such as:

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean

Example

import java.util.concurrent.atomic.AtomicInteger;
 
AtomicInteger counter = new AtomicInteger();
 
counter.incrementAndGet();

For some simple shared-state operations, atomic classes can avoid explicit locking.

However, they are not a universal replacement for synchronization. More complex operations involving multiple pieces of state may still require locking or another coordination mechanism.

Synchronization vs Lock API

Java also provides explicit locking through the java.util.concurrent.locks package.

For example:

Lock lock = new ReentrantLock();

A lock can be used like:

lock.lock();
 
try {
    // critical section
} finally {
    lock.unlock();
}

Compared with synchronized, explicit locks provide additional capabilities such as timed and interruptible lock acquisition.

For straightforward mutual exclusion, synchronized is often simpler.

Important Rules of Synchronization

Rule 1: Synchronization requires a shared monitor

Threads must synchronize using the same monitor to coordinate access.

synchronized (lock) {
    // ...
}

Rule 2: Instance synchronized methods use the object monitor

public synchronized void method() {
}

is associated with the current object.

Rule 3: Static synchronized methods use the class monitor

public static synchronized void method() {
}

is associated with the class object.

Rule 4: sleep() does not release a monitor

Thread.sleep(1000);

does not release a lock held by the sleeping thread.

Rule 5: wait() releases the relevant monitor

Calling:

lock.wait();

causes the current thread to release lock's monitor and wait.

Rule 6: Synchronize access to shared mutable state

Synchronization is generally relevant when multiple threads can access and modify the same state.

Common Mistakes

Mistake 1: Synchronizing Different Objects

This does not coordinate the threads:

synchronized (new Object()) {
    // ...
}

Every evaluation creates a different object, so threads are not using a common monitor.

Mistake 2: Calling wait() Without Owning the Monitor

This is incorrect:

lock.wait();

unless the current thread owns lock's monitor.

Correct:

synchronized (lock) {
    lock.wait();
}

Otherwise, Java throws IllegalMonitorStateException.

Mistake 3: Assuming sleep() Releases a Lock

It does not.

synchronized (lock) {
 
    Thread.sleep(5000);
 
}

The thread continues to hold lock's monitor while sleeping.

Mistake 4: Synchronizing Unnecessary Code

Synchronizing large sections unnecessarily can reduce concurrency.

Prefer protecting the actual critical section where possible.

Advantages of Synchronization

  • Helps prevent race conditions.
  • Protects shared mutable data.
  • Provides mutual exclusion for synchronized sections using the same monitor.
  • Provides memory visibility guarantees associated with monitor locking and unlocking.
  • Helps maintain consistent application state.
  • Can be used with both instance and static members.

Disadvantages of Synchronization

  • Can reduce concurrency if used excessively.
  • Threads may have to wait for locks.
  • Poor lock design can cause deadlocks.
  • Large synchronized sections can reduce performance.
  • Synchronization can make concurrent code more complex.

Key Takeaways

  • Synchronization controls concurrent access to shared resources.
  • It is mainly important when multiple threads access shared mutable state.
  • Java provides the synchronized keyword for synchronization.
  • A synchronized instance method uses the object's monitor.
  • A static synchronized method uses the class object's monitor.
  • A synchronized block allows only a specific section of code to be protected.
  • Mutual exclusion means only one thread can own a particular monitor at a time.
  • A race condition can occur when threads access shared data without proper coordination.
  • sleep() does not release a monitor.
  • wait() releases the monitor of the object on which it is called.
  • wait(), notify(), and notifyAll() are methods of Object.
  • volatile and synchronized solve different concurrency problems.
  • Atomic classes can be useful for simple atomic operations.
  • ReentrantLock provides an alternative explicit locking mechanism.
  • Poor synchronization design can cause deadlocks.

Simple Concept to Remember

Multiple Threads
       ↓
Shared Resource
       ↓
Concurrent Access
       ↓
Race Condition

↓ Synchronization ↓ Controlled Access ↓ Consistent Shared State

The key idea is: Synchronization coordinates access to shared mutable resources so that concurrent threads do not interfere with each other in unsafe ways.

See you in next page....🙂

← Thread LifecycleInter-thread Communication →
← Thread LifecycleInter-thread Communication →