Race Conditions & Critical Sections in Java

14 Apr 2026, Updated: 12 Jul 2026 3 min read
1
In multithreaded programming, multiple threads often access shared resources, such as variables, objects, or data structures.

While this enables powerful concurrent execution, it also introduces one of the most common and dangerous problems: race conditions. The main improvements are:

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 Happens 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 operation count++ 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.

In the next article, we will explore synchronization and learn how to protect critical sections using the synchronized keyword and locks.
Nagesh Chauhan

Nagesh Chauhan

Principal Software Engineer • Java • Python • Distributed Systems • AI/ML

Principal Software Engineer with 14+ years of experience designing and delivering large-scale distributed systems, cloud-native applications, and AI-powered platforms.

Passionate about solving complex engineering problems using strong data structures and algorithms, along with expertise in Java, Spring Boot, Python, System Design, Microservices, Cloud, Kafka, Elasticsearch, and Generative AI.

Share this Article

💬 Comments

Join the Discussion