A small example that completely changed how I think about shared state.
π§ The Scenario
Consider a shared integer accessed by two threads:
- Thread A increments (+1)
- Thread B decrements (-1)
Expected
0
Actual
β Unpredictable
β The Hidden Problem
At first glance, this looks safe:
```java id="u3q9mp"
value++;
But this is **not atomic**.
It actually involves three steps:
1. Read
2. Modify
3. Write
Now imagine this execution order:
```id="s7k1ye"
Thread A β Read (0)
Thread B β Read (0)
Thread A β Write (1)
Thread B β Write (-1)
Final result: -1 instead of 0
β οΈ Race Condition
This is a race condition:
- Multiple threads access shared data
- At least one modifies it
- No synchronization is applied
Result:
- Inconsistent state
- Unpredictable output
- Subtle, hard-to-debug bugs
π» Java Example
```java id="kz9r2a"
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
}
}
---
## β
Approaches to Fix
### 1. synchronized
* Simple and reliable
* Ensures mutual exclusion
* β Can reduce concurrency
---
### 2. AtomicInteger
* Lock-free
* Efficient for counters
```java id="6m2y8t"
import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
3. Locks (ReentrantLock)
- Fine-grained control
- Useful for advanced cases
- β More complex
π JavaScript Perspective
Even in JavaScript, async operations can introduce similar issues:
```javascript id="r4t8zn"
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;
}
---
## π Coordinating Async Access (Mutex Pattern)
```javascript id="g2w9xp"
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;
}
}
}
π― Key Takeaway
Concurrency bugs rarely fail loudly.
They fail silentlyβand thatβs what makes them dangerous.
π Closing Thought
This small example highlights a deeper principle:
Shared state + concurrency = risk unless carefully managed.
π·οΈ Suggested Tags
concurrency multithreading java javascript backend
π Author
Sharing practical backend learnings.
#CodeWithIshwar
Top comments (0)