Race Conditions and synchronized

Race Condition

count++ looks like one step but is three: read count, add 1, write back. If two threads read the same old value at the same time, one update is lost. This is a race condition, and the wrong result changes from run to run.

Thread A: read count (5)                      write 6
Thread B:        read count (5)   write 6
Result: 6  (expected 7) ← one increment lost
class Counter {
    int count = 0;
    void increment() { count++; }          // NOT thread-safe
}
// Two threads each call increment() 100_000 times.
// Expected 200000, typical results: 137412, 158903, 200000, 121076 ... (random)

synchronized Fixes It

A synchronized method or block lets only one thread at a time hold the object's monitor lock. Other threads wait (BLOCKED). It also guarantees visibility: changes made inside are seen by the next thread that takes the same lock.

Example

Same program with a synchronized method: always 200000.

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Runnable task = () -> {
            for (int i = 0; i < 100_000; i++) counter.increment();
        };
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println("Count = " + counter.get());
    }
}

class Counter {
    private int count = 0;
    synchronized void increment() { count++; }
    synchronized int get() { return count; }
}

Output

Count = 200000

Method vs Block

FormLocks onUse
synchronized void m()thissimple; locks the whole method
static synchronized void m()the Class objectprotect static data
synchronized (lockObj) { ... }lockObjlock only the critical part (better throughput)

Tip: Use a private final Object lock = new Object(); for synchronized blocks, so outside code cannot lock on your object and cause surprises.