Understanding race conditions and identifying critical sections is the first step toward writing safe and correct concurrent programs.
What is a Race Condition?
A race condition occurs when multiple threads access and modify shared data at the same time, and the final result depends on the timing and order of execution.Since thread scheduling is unpredictable, the outcome becomes inconsistent and often incorrect.
In simple terms: "Multiple threads are racing to update shared data, and the result depends on who wins."
What is a Critical Section?
A critical section is a part of the code where shared resources are accessed or modified.If multiple threads execute this section simultaneously without control, it can lead to data corruption or inconsistent results.
Example of a critical section:
count++; // NOT an atomic operation
Even though it looks like a single line, it actually involves multiple steps:- Read value of
count- Increment value
- Write value back
If two threads execute this simultaneously, updates can be lost. Both threads may read the same initial value before either writes the updated result, causing one update to overwrite the other.
As a result, even though two increments occurred logically, only one is reflected in the final value.
What Goes Wrong Without Synchronization
Without proper synchronization, multiple issues can occur:1. Lost Updates: Two threads read the same value, increment it, and write it back. One update overwrites the other.
2. Inconsistent Data: Threads may see partially updated or stale values due to lack of memory visibility guarantees.
3. Non-Deterministic Behavior: The program produces different results each time it runs, making debugging extremely difficult.
4. Data Corruption: Shared data structures can become invalid if multiple threads modify them simultaneously.
Example: Race Condition in Action
Consider a simple counter incremented by multiple threads.class Counter {
int count = 0;
void increment() {
count++;
}
}
public class Main {
static void main(String[] args) throws Exception {
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.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final count: " + counter.count);
}
}
Expected Output:
Final count: 2000
Actual Output (may vary):
Final count: 1737
The result is inconsistent because multiple threads are modifying count without synchronization.
Why Does This Happen?
The operationcount++ is not atomic. Internally, it works like this:
// simplified breakdown
int temp = count;
temp = temp + 1;
count = temp;
Now imagine:
- Thread A reads
count = 5- Thread B reads
count = 5- Thread A writes
6- Thread B also writes
6
One increment is lost.
Another Example: Bank Account Problem
class BankAccount {
int balance = 100;
void withdraw(int amount) {
if (balance >= amount) {
balance = balance - amount;
}
}
}
If two threads withdraw money simultaneously:
- Both may see sufficient balance
- Both proceed to withdraw
- Balance may go negative or incorrect
This is a classic race condition leading to data inconsistency.
Race conditions occur because:
- Threads share memory
- Execution order is unpredictable
- Operations are not atomic
How to Identify Critical Sections
A code section is critical if:- It accesses shared data
- It modifies shared state
- Multiple threads can execute it simultaneously
Such sections must be protected using synchronization mechanisms (covered in next article).
Race conditions are one of the most fundamental problems in concurrent programming. They arise when multiple threads access shared data without proper coordination, leading to unpredictable and incorrect results.
By identifying critical sections and understanding what goes wrong without synchronization, developers can take the first step toward writing safe multithreaded applications.
In the next article, we will explore synchronization in Java and learn how to protect critical sections using the
synchronized keyword and locks.
Join the discussion