Synchronization in Java

Last Updated : 20 Jan 2026

Synchronization in Java is a mechanism that controls access to shared resources to prevent data inconsistency in multithreaded programs.

In this chapter, you will learn how synchronization works in Java, why it is needed, and how to use synchronized methods and blocks to ensure thread-safe execution.

Understanding Threads and Shared Resources

A thread represents an independent path of execution within a program. When multiple threads access shared resources concurrently, unpredictable interleaving of operations may lead to data inconsistency. Consider a scenario where two threads increment a shared variable at the same time:

If two threads execute the increment() method simultaneously, both may read the same value of count, increment it, and write it back. As a result, one update can overwrite the other, leading to incorrect final values. This situation is known as a race condition.

Introducing Synchronization

Synchronization addresses such issues by ensuring that only one thread at a time can access a critical section of code or a shared resource associated with an object. This exclusive access prevents race conditions and maintains data consistency.

Java provides two primary mechanisms for synchronization:

  • Synchronized methods
  • Synchronized blocks

These mechanisms allow developers to control thread access and ensure safe execution in multithreaded environments.

Synchronized Methods

In Java, you can declare entire methods as synchronized which prevent multiple threads from accessing the method simultaneously. With this, synchronization becomes a simpler process because the mechanism is applied to all invocations of the synchronized method automatically.

Example

In this example, the SynchronizedCounter class uses synchronized methods to ensure that only one thread at a time can modify or access the shared count variable, preventing race conditions and ensuring thread safety.

With this modification, concurrent calls to increment() or getCount() will be synchronized, preventing race conditions.

Synchronized Blocks

Synchronized block provides exclusive access to shared resources, and only one thread is allowed to execute it in the same time frame. It's structured as follows:

This monitor object or lock is the subject. While only one thread can be holding a lock on a monitor object at one instance. Other threads that want to go into the synchronized blocks with this object must wait till the lock becomes available.

Intrinsic Locks and Synchronization

In Java, every object automatically has an intrinsic lock (or monitor lock) associated to it. The moment a thread enters the synchronized block or method, it gets the lock for the object, and then no other thread is allowed to enter the synchronized block or method for that object until the lock is released.

1. Deadlocks

On the one hand, synchronization ensures that race condition is ruled out, but on the other hand, it may lead to deadlocks if not used critically. Stalemates could result as two or more threads are ceaseless when they are waiting for resources from each other. Avoid deadlocks by ordering the locks and releasing them in an opposite sequence of both.

2. Locking Granularity

The best locking granularity has to be selected and has to avoid contention and thus be good for performance. Leaning too broadly can reduce concurrency, while leaning too finely can lead to overhead increase. Identification of the only section of the code which needs to be the only one having an exclusive access to the shared resources and synchronization of that section alone.

3. Concurrent Collections

Java contains a thread-safe versions of the common collections classes that found in the java.util.concurrent package, including the ConcurrentHashMap and ConcurrentLinkedQueue. These classes provide internal synchronization mechanisms that guarantee the thread safety without giving up the synchronization control to the user.

4. Volatile Keyword

Furthermore, volatile keyword may be used to maintain the visibility of changes made to the variables among the threads. For variables declared as volatile, their value will always be read directly from memory and the writes to them will be visible to all the other threads immediately. However, volatile does not ensure as such for complex instructions such as incrementing.

5. Atomic Classes

Java gives atomic classes in the java.util.concurrent.atomic package including Atomic Integer and Atomic Long, which offer atomic operations of variables without using explicit synchronization. These kind of classes use hardware's low level atomic operations to make thread safety.

Types of Thread Synchronization

There are two types of thread synchronization in Java: mutual exclusive and inter-thread communication.

  1. Mutual Exclusive
    1. Synchronized method.
    2. Synchronized block.
    3. Static synchronization.
  2. Cooperation (Inter-thread communication in Java)

1. Mutual Exclusion

Mutual exclusion prevents threads from interfering with one another while accessing shared data. It ensures that only one thread can execute a critical section at a time.

In Java, mutual exclusion can be achieved in the following ways:

  • Using synchronized methods
  • Using synchronized blocks
  • Using static synchronization

Concept of Lock

Synchronization is built around an internal entity called a lock or monitor. Every object in Java has an associated lock. When a thread enters a synchronized method or block:

  • It acquires the object's lock
  • Executes the critical section
  • Releases the lock when execution completes

Other threads must wait until the lock is released before accessing the synchronized code.

From Java 5 onward, the java.util.concurrent.locks package provides advanced lock implementations for more flexible synchronization.

Understanding the Problem without Synchronization

When synchronization is not used, multiple threads can access shared resources simultaneously, leading to unpredictable output.

Consider the following example where the method is not synchronized:

Compile and Run

Output:

5
100
10
200
15
300
20
400
25
500

The output is inconsistent because both threads execute the method simultaneously.

2. Cooperation (Inter-thread Communication)

Inter-thread communication allows multiple threads to coordinate their execution by communicating with each other instead of competing for a resource. This cooperation is achieved using the wait(), notify(), and notifyAll() methods of the Object class.

Consider a restaurant scenario, where the Cook thread prepares the food, the Customer thread waits until the food is ready, and once the food is prepared, the cook notifies the customer to proceed.

In the below example code, here one thread waits for a condition to be fulfilled while another thread performs an action and notifies the waiting thread to continue execution.

Example

Output:

Customer is waiting for food...
Cook is preparing food...
Customer is served food.