Types of Synchronization
In Java, there are two types of synchronization:
1. Process Synchronization
Process synchronization deals with the coordination of processes that use shared data or resources. It involves mechanisms like locks, semaphores, & monitors to ensure that only one process can access the shared resource at a time. Process synchronization is used in multi-process environments where multiple processes need to communicate & share resources.
2. Thread Synchronization
Thread synchronization is used to coordinate the actions of multiple threads within a single process. It ensures that threads access shared data or resources in a mutually exclusive manner, preventing data inconsistencies & race conditions. Thread synchronization is achieved using synchronized methods or synchronized blocks in Java.
Thread synchronization is more commonly used in Java programs as it deals with the coordination of threads within a single process. It is essential for maintaining data integrity & avoiding conflicts when multiple threads access shared resources concurrently.
Thread Synchronization
Thread synchronization in Java is achieved using the `synchronized` keyword. When a method or block of code is marked as `synchronized`, it ensures that only one thread can execute that code at a time. This is known as mutual exclusion.
There are two ways to achieve thread synchronization in Java:
1. Synchronized Methods
When a method is declared as `synchronized`, it means that only one thread can execute that method at a time for a particular instance of the class. If multiple threads try to call the synchronized method simultaneously, only one thread will be allowed to execute, while the others will wait until the lock is released.
Example:
public synchronized void updateCount() {
// Code that needs to be synchronized
}
2. Synchronized Blocks
Synchronized blocks allow you to synchronize a specific portion of code within a method. Instead of synchronizing the entire method, you can use a synchronized block to lock only the necessary code segment.
Example:
public void updateCount() {
synchronized (this) {
// Code that needs to be synchronized
}
}
In the above example, the `synchronized` block is used to synchronize the code inside the block. The `this` keyword is used as the lock object, indicating that the lock is associated with the current instance of the class.
Note: Thread synchronization ensures that multiple threads access shared resources in a mutually exclusive manner, preventing data inconsistencies and race conditions.
Mutual Exclusive
Mutual exclusion is a key concept in thread synchronization. It ensures that only one thread can access a shared resource or execute a critical section of code at a time. When a thread enters a critical section, it locks the associated resource or code block, preventing other threads from accessing it simultaneously.
In Java, mutual exclusion is achieved using the `synchronized` keyword. When a thread enters a synchronized method or block, it acquires the lock associated with the object. Other threads that try to enter the same synchronized method or block will be blocked until the lock is released by the thread that currently holds it.
For example :
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public synchronized int getCount() {
return count;
}
}
In the above example, the `Counter` class has three synchronized methods: `increment()`, `decrement()`, and `getCount()`. When a thread calls any of these methods, it acquires the lock associated with the `Counter` instance. This ensures that only one thread can execute these methods at a time, preventing concurrent access to the shared `count` variable.
Mutual exclusion helps to maintain data integrity and prevent race conditions. It guarantees that only one thread can modify the shared data at a time, ensuring consistent and predictable results.
Concept of Lock in Java
In Java, a lock is a mechanism used to achieve synchronization and ensure mutual exclusion. Every object in Java has an associated lock, also known as a monitor lock or intrinsic lock. When a thread enters a synchronized method or block, it acquires the lock associated with the object. The lock is released when the thread exits the synchronized method or block.
Here are some key points about locks in Java:
1. Acquiring a Lock
- When a thread enters a synchronized method or block, it automatically acquires the lock associated with the object.
- If the lock is already held by another thread, the current thread will be blocked until the lock becomes available.
- Only one thread can hold the lock at a time.
2. Releasing a Lock
- When a thread exits a synchronized method or block, it automatically releases the lock.
- If other threads are waiting for the lock, one of them will acquire the lock and continue execution.
3. Reentrant Lock
- Java locks are reentrant, which means that if a thread already holds the lock and enters a synchronized method or block on the same object, it can continue execution without being blocked.
- Reentrant locks allow a thread to acquire the same lock multiple times, as long as it releases the lock an equal number of times.
4. Avoiding Deadlock
- Deadlock occurs when two or more threads are waiting for each other to release locks, resulting in a circular dependency.
- To avoid deadlock, it's important to ensure that locks are acquired and released in a consistent order and that threads do not hold locks for unnecessarily long durations.
For example :
public class SharedResource {
public synchronized void method1() {
// Code that requires the lock
}
public synchronized void method2() {
// Code that requires the lock
}
}
In the above example, both `method1()` and `method2()` are synchronized methods. When a thread calls either of these methods, it acquires the lock associated with the `SharedResource` instance. Other threads that try to call these methods will be blocked until the lock is released.
Understanding the problem without Synchronization
To understand the importance of synchronization, let's consider an example scenario without synchronization. Suppose we have a shared counter variable that multiple threads can access and modify concurrently.
public class Counter {
private int count = 0;
public void increment() {
count++;
}
public void decrement() {
count--;
}
public int getCount() {
return count;
}
}
In the above code, the `Counter` class has methods to increment, decrement, & retrieve the value of the `count` variable. Now, let's consider a situation where multiple threads access these methods simultaneously.
public class MainThread {
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.decrement();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}
In this example, we create two threads, `t1` & `t2`, which increment & decrement the `count` variable 1000 times each. After both threads finish their execution, we expect the final count to be zero (1000 increments - 1000 decrements = 0).
However, when we run this code without synchronization, the output may vary each time. The final count may not always be zero due to race conditions. This happens because multiple threads access & modify the shared `count` variable concurrently without any coordination.
The lack of synchronization can lead to inconsistent & unpredictable results. Threads may overwrite each other's changes, leading to lost updates & incorrect values.
To solve this problem, we need to use synchronization mechanisms like synchronized methods or blocks to ensure that only one thread can access the shared variable at a time, maintaining data integrity & preventing race conditions.
Java Synchronized Method
To address the issues that arise without synchronization, Java provides the `synchronized` keyword. By declaring a method as `synchronized`, we ensure that only one thread can execute that method at a time for a particular instance of the class.
Let's modify the previous `Counter` class to use synchronized methods:
public class SynchronizedCounter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
public synchronized int getCount() {
return count;
}
}
In the `SynchronizedCounter` class, we have added the `synchronized` keyword to the `increment()`, `decrement()`, & `getCount()` methods. This means that when a thread enters any of these methods, it acquires the lock associated with the `SynchronizedCounter` instance. Other threads that try to call these methods will be blocked until the lock is released.
Now, let's use the `SynchronizedCounter` class in the `MainThread`:
Java
public class MainThread {
public static void main(String[] args) {
SynchronizedCounter counter = new SynchronizedCounter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.decrement();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}

You can also try this code with Online Java Compiler
Run Code
Output
Final count: 0
With the synchronized methods in place, when we run this code, the final count will always be zero. This is because the `increment()` & `decrement()` methods are executed atomically by each thread. Only one thread can execute these methods at a time, preventing race conditions & ensuring data consistency.
Synchronized methods provide a convenient way to achieve thread synchronization in Java. They ensure that critical sections of code are executed atomically, maintaining data integrity & preventing concurrent access issues.
Example of synchronized method by using anonymous class
Let's take a look at an example that shows the use of synchronized methods with an anonymous class:
Java
public class SynchronizedExample {
public static void main(String[] args) {
final PrinterQueue printerQueue = new PrinterQueue();
// Create multiple threads using anonymous class
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
printerQueue.printJob("Document 1");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
printerQueue.printJob("Document 2");
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
printerQueue.printJob("Document 3");
}
});
// Start the threads
thread1.start();
thread2.start();
thread3.start();
}
}
class PrinterQueue {
// synchronized method
public synchronized void printJob(String document) {
System.out.println("Printing: " + document);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Completed: " + document);
}
}

You can also try this code with Online Java Compiler
Run Code
In this example, we have a `PrinterQueue` class with a synchronized `printJob()` method. This method simulates printing a document by displaying a message, pausing for one second using `Thread.sleep()`, & then displaying a completion message.
In the `main()` method of the `SynchronizedExample` class, we create three threads using anonymous classes. Each thread invokes the `printJob()` method of the `PrinterQueue` instance with a different document name.
When we run this code, the output will be :
Printing: Document 1
Completed: Document 1
Printing: Document 2
Completed: Document 2
Printing: Document 3
Completed: Document 3
The order of execution may vary, but the important thing to note is that the `printJob()` method is executed atomically by each thread due to the `synchronized` keyword. Only one thread can execute the `printJob()` method at a time, ensuring that the printing of each document is completed before another thread can start printing.
Frequently Asked Questions
What happens if multiple threads try to execute a synchronized method simultaneously?
When multiple threads attempt to execute a synchronized method simultaneously, only one thread is allowed to enter the method & acquire the lock. The other threads are blocked & wait until the lock becomes available.
Can synchronized methods be overridden in subclasses?
Yes, synchronized methods can be overridden in subclasses. However, the synchronization is based on the object instance, not the class. Each object has its own lock, so synchronization is achieved independently for each object instance.
Is it possible to synchronize static methods in Java?
Yes, static methods can be synchronized in Java. When a static method is synchronized, the lock is associated with the class object rather than the instance. Only one thread can execute the synchronized static method at a time, across all instances of the class.
Conclusion
In this article, we learned about Java synchronized methods & their role in achieving thread synchronization. Synchronization is essential to prevent data inconsistencies & race conditions when multiple threads access shared resources concurrently. With the help of synchronized keyword, we ensure that only one thread can execute a synchronized method at a time, providing mutual exclusion & maintaining data integrity.
You can also practice coding questions commonly asked in interviews on Coding Ninjas Code360.
Also, check out some of the Guided Paths on topics such as Data Structure and Algorithms, Competitive Programming, Operating Systems, Computer Networks, DBMS, System Design, etc., as well as some Contests, Test Series, and Interview Experiences curated by top Industry Experts.