Synchronization — synchronized, volatile, atomic
Thread-এর মধ্যে state নিরাপদে share করা
1. The Race Condition
When two threads read and write the same variable, things go wrong. counter++ looks like one
step but is actually three — read, add, write. If two threads interleave those three steps, one increment
is lost. That is a race condition, and it is the root cause of almost every concurrency bug.
counter++ দেখতে একটি operation, কিন্তু ভেতরে তিনটি — read, add, write। দুই thread এই তিন step interleave করলে একটি increment হারিয়ে যায়। এটাই race condition — প্রায় সব concurrency bug-এর মূল কারণ।
class Main {
static int counter = 0;
public static void main(String[] args) throws Exception {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) counter++;
};
Thread a = new Thread(task);
Thread b = new Thread(task);
a.start(); b.start();
a.join(); b.join();
// expected 20000 — usually less (lost updates)
System.out.println("counter = " + counter);
}
}
2. synchronized — Mutual Exclusion
The classic fix: put the critical section inside a synchronized block. Exactly one thread can
hold a given monitor lock at a time. You can mark a whole method synchronized, or a specific
block with a lock object.
synchronized block-এ রাখুন। একই monitor lock-এ একসাথে একটি মাত্র thread থাকতে পারে। পুরো method বা একটি block-কে একটি lock object দিয়ে synchronized করা যায়।
class Main {
static int counter = 0;
static final Object LOCK = new Object();
public static void main(String[] args) throws Exception {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
synchronized (LOCK) {
counter++;
}
}
};
Thread a = new Thread(task);
Thread b = new Thread(task);
a.start(); b.start();
a.join(); b.join();
System.out.println("counter = " + counter); // always 20000
}
}
3. volatile — Visibility, Not Atomicity
Without synchronization, one thread's writes may never be seen by another — modern CPUs cache values in
registers. volatile on a field forces every read/write to go to main memory, giving
visibility. But it does not make compound operations atomic. volatile int x; x++;
is still a race.
volatile বললে প্রতিটি read/write main memory-তে যায় — visibility নিশ্চিত। কিন্তু এটি atomicity দেয় না। volatile int x; x++; এখনো race।
class Main {
static volatile boolean done = false;
public static void main(String[] args) throws Exception {
Thread worker = new Thread(() -> {
int spins = 0;
while (!done) spins++;
System.out.println("worker saw done; spun " + spins + " times");
});
worker.start();
Thread.sleep(30);
done = true; // visible to worker thanks to volatile
worker.join();
}
}
4. Atomics — Lock-Free Counters
java.util.concurrent.atomic provides types like AtomicInteger,
AtomicLong, AtomicReference. Their incrementAndGet,
compareAndSet, etc. are atomic at the hardware level (compare-and-swap). They are fast and
perfect for counters.
AtomicInteger, AtomicLong, AtomicReference — lock ছাড়াই atomic। CPU-র compare-and-swap (CAS) instruction ব্যবহার করে। counter-এর জন্য সবচেয়ে দ্রুত সমাধান।
import java.util.concurrent.atomic.*;
class Main {
static final AtomicInteger counter = new AtomicInteger();
public static void main(String[] args) throws Exception {
Runnable task = () -> {
for (int i = 0; i < 10000; i++) counter.incrementAndGet();
};
Thread a = new Thread(task), b = new Thread(task);
a.start(); b.start();
a.join(); b.join();
System.out.println("counter = " + counter.get()); // 20000
}
}
5. ReentrantLock — Explicit Locking
ReentrantLock is like synchronized but with extra features: timed acquisition,
interruptible waits, and fairness. Use it only when you actually need those features — otherwise
synchronized is simpler and just as fast.
ReentrantLock = synchronized + extra feature (timed acquire, interruptible wait, fairness)। এই feature দরকার হলেই ব্যবহার করুন, নাহলে synchronized-ই simple ও same-fast।
import java.util.concurrent.locks.*;
class Main {
static final ReentrantLock lock = new ReentrantLock();
static int balance = 100;
static void withdraw(int amt) {
lock.lock();
try {
if (balance >= amt) balance -= amt;
} finally {
lock.unlock(); // always release
}
}
public static void main(String[] args) throws Exception {
Runnable r = () -> { for (int i = 0; i < 20; i++) withdraw(1); };
Thread a = new Thread(r), b = new Thread(r);
a.start(); b.start();
a.join(); b.join();
System.out.println("balance = " + balance);
}
}
6. Deadlock — and How to Avoid It
Two threads hold a lock each and wait for the other's lock — nothing moves forever. The fix is simple:
always acquire locks in the same global order. If every thread locks A before
B, deadlock is impossible.
A পরে B lock করলে deadlock অসম্ভব।
7. Vocabulary
| Term | Meaning | বাংলায় |
|---|---|---|
| Race condition | Outcome depends on thread scheduling — a bug. | thread scheduling-এর উপর ফলাফল নির্ভর — bug। |
| Critical section | Code that must run without interference. | বিনা হস্তক্ষেপে চলতে হওয়া code। |
| synchronized | Keyword that gives mutual exclusion via a monitor lock. | monitor lock দিয়ে mutual exclusion। |
| volatile | Field modifier guaranteeing visibility, not atomicity. | visibility দেয়, atomicity নয়। |
| CAS | Compare-And-Swap — hardware atomic instruction. | hardware-level atomic instruction। |
| Deadlock | Two+ threads stuck waiting on each other. | একে অপরের lock-এর জন্য চিরকাল অপেক্ষা। |
| Reentrant | A thread can re-acquire a lock it already holds. | নিজের ধরা lock আবার ধরতে পারে। |
8. Practice Problems
-
Fix a lost-update counter with
synchronized.lost-update-এ আক্রান্ত counter-কেsynchronizedদিয়ে fix করুন।Show Answer (উত্তর দেখুন)
Main.javaclass Main { static int c = 0; static final Object L = new Object(); public static void main(String[] args) throws Exception { Runnable r = () -> { for (int i = 0; i < 5000; i++) synchronized (L) { c++; } }; Thread a = new Thread(r), b = new Thread(r); a.start(); b.start(); a.join(); b.join(); System.out.println(c); } } -
Explain in 2 sentences why
volatilealone cannot makex++safe.দুই বাক্যে বলুন —volatileএকাx++-কে নিরাপদ করতে পারে না কেন।Show Answer (উত্তর দেখুন)
Answer:
volatileguarantees that reads and writes cross the memory boundary correctly, butx++is three separate operations — read, add, write — and two threads can still interleave those steps. Only an atomic primitive (AtomicInteger) or a lock closes that window.volatileশুধু read/write main memory-তে যাওয়া guarantee দেয়, কিন্তুx++তিনটি step (read, add, write) — দুই thread interleave করলেই loss।AtomicIntegerবা lock-ই সমাধান। -
Use
AtomicIntegerto count how many times two threads together called a method.AtomicIntegerদিয়ে গুনুন — দুটি thread একসাথে method-টি কতবার call করেছে।Show Answer (উত্তর দেখুন)
Main.javaimport java.util.concurrent.atomic.*; class Main { static final AtomicInteger calls = new AtomicInteger(); static void hello() { calls.incrementAndGet(); } public static void main(String[] args) throws Exception { Runnable r = () -> { for (int i = 0; i < 1000; i++) hello(); }; Thread a = new Thread(r), b = new Thread(r); a.start(); b.start(); a.join(); b.join(); System.out.println(calls.get()); } } -
Write a thread-safe
Counterclass withincrementandget.একটি thread-safeCounterclass লিখুন —incrementওget।Show Answer (উত্তর দেখুন)
Main.javaclass Main { static class Counter { private int n = 0; synchronized void increment() { n++; } synchronized int get() { return n; } } public static void main(String[] args) throws Exception { Counter c = new Counter(); Runnable r = () -> { for (int i = 0; i < 1000; i++) c.increment(); }; Thread a = new Thread(r), b = new Thread(r); a.start(); b.start(); a.join(); b.join(); System.out.println(c.get()); } } -
In 3 sentences, describe the golden rule that prevents deadlock.তিন বাক্যে deadlock এড়ানোর সোনালী নিয়ম বলুন।
Show Answer (উত্তর দেখুন)
Answer: Always acquire multiple locks in the same total order across all threads — define a well-known ranking and never take lock B while holding lock A if A comes after B in that ranking. If you cannot enforce an order (dynamic objects), use
tryLockwith a timeout and back off when you fail. Finally, keep critical sections short — fewer chances to collide, less damage if you do.সব thread-এ একই global order-এ lock নিন। dynamic object হলে
tryLock+ timeout, ব্যর্থ হলে back off। critical section ছোট রাখুন — কম collision, কম ক্ষতি।
Summary — Module 38
Shared mutable state is concurrency's worst enemy. synchronized gives mutual exclusion,
volatile gives visibility but not atomicity, atomics give lock-free updates for simple fields,
and ReentrantLock gives you advanced control when you need it. Write down each field's
concurrency policy, lock in a consistent global order, and keep critical sections short.
synchronized = mutual exclusion, volatile = visibility (atomicity নয়), atomics = lock-free, ReentrantLock = advanced control। প্রতিটি field-এর policy লিখুন, consistent order-এ lock নিন, critical section ছোট রাখুন।