In this article, I am going to explain what is Race condition? Where Race condition are not a concern in java code and different ways to prevent them.

What is Race Condition?
Race condition simply means when multiple threads access a shared resource without proper synchronization, leading to unpredictable behavior
It happens when multiple threads simultaneously access and modify a shared resource, causing unpredictable behavior because the final outcome depends on the execution timing of the threads. This can lead to incorrect results, data corruption, or unexpected program behavior.
Let me take an example to explain this definition.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//Counter class class Counter { //state variable private int count = 0; public void increment() { count++; // Not thread safe } public int getCount() { return count; } } |
In increment() method we are incrementing the value of count. At the CPU level, the Count++ operation is a three step process.
- Read the current value of count
- Add/subtract 1
- Write the new value back to count
Now, suppose two threads simultaneously call increment() method with count is 0.
- Thread A reads count (0)
- Thread B reads count (0)
- Thread A adds 1 and writes 1
- Thread B adds 1 and writes 1
- The final result is 1 instead of the expected 2
This is called a race condition, and it leaves the value of count in an inconsistent state.
You only need worry about race condition where classes have shared mutable state. If a class is stateless, you generally don’t have to worry about race condition
A class which doesn’t have shared state variable, you don’t have to worry about race condition. Let me take an example –
1 2 3 4 5 6 7 8 |
//Calculator class public class Calculator { // No shared state, just local variables public int add(int a, int b) { return a + b; } } |
In this class, we don’t have any shared mutable state. Each thread have their own call stack, where the addition operation happen and also they don’t have to share the result. So no synchronization needed, as each call operates independently.
How to Prevent Race Condition in Java Code
Using Synchronization Keyword
Synchronized keyword ensure only one thread at a time increment the value of count, preventing race conditions and data inconsistency.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//Using synchronized keyword class Counter { //state variable private int count = 0; //Thread safe public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } |
Let’s assume that when two threads simultaneously call the increment() method, one of the threads gets the lock and executes count++. After that, it releases the lock, and then the next thread acquires the lock and does the same thing. In this way, this method is thread-safe.
Using ReentrantLock
ReentrantLock also serves the same purpose but provides greater flexibility and control over thread synchronization.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import java.util.concurrent.locks.ReentrantLock; class Counter { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); // Acquire lock try { count++; // Critical section } finally { lock.unlock(); // Release lock } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }<strong> </strong> |
Using AtomicInteger
Using AtomicInteger
is another way to prevent race condition without using synchronized
or ReentrantLock. It is
designed to facilitate atomic operations on a single int value. Atomic classes ensure thread-safe updates without locks, improving performance.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Using AtomicInteger class Counter { private AtomicInteger count = new AtomicInteger(0); // Atomic variable public void increment() { count.incrementAndGet(); // Atomic increment } public int getCount() { return count.get(); // Atomic read } } |
Use AtomicInteger
when you only need atomic updates to a single variable. For complex synchronization between multiple variables use ReentrantLock
or synchronized
.
Conclusion
- Race conditions occur when multiple threads access shared data unsafely.
- They lead to inconsistent or incorrect results.
- Fixes include synchronized, ReentrantLock, and Atomic variables.