DEV Community

CodeWithIshwar
CodeWithIshwar

Posted on

⚠️ Why a Simple Integer Breaks in Concurrency (Race Condition Explained)

Even a simple shared integer can produce unpredictable results.


🧠 The Scenario

Let’s say we have a shared variable:

  • One thread increments (+1)
  • Another thread decrements (-1)

Expected result:

0
Enter fullscreen mode Exit fullscreen mode

Actual result:

❌ Unpredictable
Enter fullscreen mode Exit fullscreen mode

❗ What’s Going Wrong?

At first glance, this looks harmless:

value++;
Enter fullscreen mode Exit fullscreen mode

But this is NOT atomic.

It actually involves three steps:

  • Read
  • Modify
  • Write

Now imagine two threads executing this at the same time:

Thread A → Read (0)
Thread B → Read (0)
Thread A → Write (1)
Thread B → Write (-1)
Enter fullscreen mode Exit fullscreen mode

Final result: -1 instead of 0


⚠️ Race Condition

This is a classic race condition:

  • Multiple threads access shared data
  • At least one modifies it
  • No proper synchronization

Result:

  • Inconsistent data
  • Unpredictable behavior
  • Hard-to-debug issues

💻 Java Example

class Counter {
    int value = 0;

    void increment() {
        value++; // not atomic
    }

    void decrement() {
        value--; // not atomic
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) counter.increment();
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) counter.decrement();
        });

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(counter.value); // ❌ unpredictable
    }
}
Enter fullscreen mode Exit fullscreen mode

✅ Fixes

1. synchronized

  • Easy to use
  • Ensures mutual exclusion
  • ❌ Can block threads
synchronized void increment() {
    value++;
}
Enter fullscreen mode Exit fullscreen mode

2. AtomicInteger

  • Lock-free and efficient
  • Ideal for counters
import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
Enter fullscreen mode Exit fullscreen mode

3. Locks (ReentrantLock)

  • More control
  • Useful for complex cases
  • ❌ More verbose

🌐 JavaScript Version (Async Race Condition)

Even though JavaScript is single-threaded, async operations can still create race conditions:

let counter = 0;

async function increment() {
  let temp = counter;
  await Promise.resolve();
  counter = temp + 1;
}

async function decrement() {
  let temp = counter;
  await Promise.resolve();
  counter = temp - 1;
}
Enter fullscreen mode Exit fullscreen mode

🔐 JavaScript Fix (Mutex)

class Mutex {
  constructor() {
    this.locked = false;
    this.queue = [];
  }

  lock() {
    return new Promise(resolve => {
      if (!this.locked) {
        this.locked = true;
        resolve();
      } else {
        this.queue.push(resolve);
      }
    });
  }

  unlock() {
    if (this.queue.length > 0) {
      const next = this.queue.shift();
      next();
    } else {
      this.locked = false;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

🎯 Key Takeaway

Concurrency bugs don’t fail loudly.
They fail silently.

And that’s what makes them dangerous.


🚀 Final Thoughts

This small example completely changed how I think about shared state.

If you’re working in backend or distributed systems, this is something you must understand deeply.


🏷️ Tags (add on DEV)

#java #javascript #concurrency #backend #programming


📌 About Me

Sharing what I learn as I grow.

#CodeWithIshwar

Top comments (0)