Clarifying the exact difference between Data Race and Race Condition in Java
03:01 21 May 2026

I am diving deeper into Java concurrency and realized I might have been using the terms "Data Race" and "Race Condition" interchangeably. Based on my recent research, they are distinct concepts, but I want to make sure my mental model and examples are completely correct.
Here is my current understanding:
1. Data Race (Hardware/Memory level)
This happens when two or more threads access the same memory location concurrently, at least one is a write, and there is no explicit synchronization.

public class Counter {
    private int count = 0;
    
    public void increment() {
        count++; // Data Race here?
    }
}

My assumption: Because count++ is not atomic (read-modify-write), and there is no synchronized block or volatile keyword, threads will read stale data and overwrite each other. This is a classic Data Race.

2. Race Condition (Logic/Timing level)
This happens when the correctness of the program depends on the unpredictable timing or ordering of thread execution. A program can be completely free of Data Races (using thread-safe collections) but still suffer from a Race Condition.

private final ConcurrentHashMap cache = new ConcurrentHashMap<>();

public void loadUser(String userId) {
    if (!cache.containsKey(userId)) { 
        // Timing gap: Another thread could insert the user right here
        User user = database.getUser(userId); 
        cache.put(userId, user); 
    }
}

My assumption: ConcurrentHashMap ensures memory safety, so there is no Data Race here. However, there is a Race Condition because the logic fails if threads interleave between the containsKey check and the put action (resulting in multiple redundant database queries).

My Questions:

  1. Are my definitions and code examples above technically accurate for distinguishing the two?

  2. Is it possible for a Java program to have a Data Race but not a Race Condition? (If so, what would be a practical example?)

  3. For the Check-Then-Act issue in the second example, I know computeIfAbsent is the modern fix. But conceptually, does fixing a Race Condition always require expanding the critical section to cover both the "Check" and the "Act" atomically?

java multithreading concurrency race-condition