Modern Java
Introduction to Concurrency and Thread Safety
আপনি একটি free preview lesson দেখছেন।
Lesson Overview
এ পর্যন্ত আমাদের বেশিরভাগ Java code sequential ছিল।
Meaning:
একটি operation
তারপর আরেকটি operation
তারপর আরেকটি operation
Example:
loadCourse();
validateCourse();
publishCourse();
কিন্তু real applications-এ অনেক কাজ একসঙ্গে progress করতে পারে।
Examples:
Multiple users একই server ব্যবহার করছে
একাধিক request একই service-এ আসছে
Background task চলছে
File processing হচ্ছে
Message processing হচ্ছে
Network call-এর জন্য অপেক্ষা হচ্ছে
একাধিক task CPU ব্যবহার করছে
এই ধরনের environment-এ আমাদের বুঝতে হয়:
Concurrency
Thread
Shared State
Race Condition
Thread Safety
এই lesson-এ আমরা শিখব:
- Process কী
- Thread কী
- Concurrency কী
- Parallelism কী
- Concurrency এবং Parallelism-এর difference
- Shared mutable state
- Race condition
- Atomicity
- কেন
count++atomic নয় - Thread safety
- Critical section
synchronized- Intrinsic lock / monitor-এর basic idea
- Visibility
volatilevolatileকী solve করেvolatileকী solve করে না- Immutability
- Stateless design
- Thread confinement
- Compound operations
- Check-then-act race
- Thread-safe design-এর practical principles
- কেন concurrency bugs difficult
Why Learn Concurrency?
Backend application usually এক user-এর জন্য একবার run করে না।
একই সময়:
User A
User B
User C
User D
service-কে request করতে পারে।
একটি server process-এর মধ্যে অনেক request handling task concurrently progress করতে পারে।
তাই code যদি shared state use করে, প্রশ্ন আসে:
একই data দুইটি thread একসঙ্গে change করলে কী হবে?
Concurrency fundamentals না বুঝলে এমন bugs হতে পারে:
lost updates
incorrect counters
duplicate processing
inconsistent state
random failures
hard-to-reproduce bugs
What Is a Process?
একটি running program-কে সাধারণভাবে একটি:
Process
হিসেবে ভাবা যায়।
Example:
Java application start হলো
↓
Operating System একটি process চালাচ্ছে
Process-এর নিজের resources থাকে।
Examples:
memory
open files
network connections
threads
What Is a Thread?
একটি process-এর ভিতরে execution-এর একটি path হলো:
Thread
একটি Java process-এর মধ্যে multiple threads থাকতে পারে।
Conceptually:
Java Process
├── Thread 1
├── Thread 2
├── Thread 3
└── Thread 4
প্রতিটি thread আলাদা execution flow চালাতে পারে।
Main Thread
Simple Java application:
public class Main {
public static void main(
String[] args
) {
System.out.println(
"Hello"
);
}
}
main() method সাধারণত application-এর main thread-এ start হয়।
Creating a Simple Thread
Basic example:
Thread worker =
new Thread(
() ->
System.out.println(
"Running in worker thread"
)
);
worker.start();
এখানে:
start()
নতুন thread-এ task execute করতে দেয়।
Do Not Call run() Directly by Mistake
Given:
Thread worker =
new Thread(
() ->
doWork()
);
This:
worker.run();
normal method call-এর মতো current thread-এই execute করবে।
But:
worker.start();
নতুন thread start করে।
Thread Example
public class Main {
public static void main(
String[] args
) {
Thread first =
new Thread(
() ->
print(
"First"
)
);
Thread second =
new Thread(
() ->
print(
"Second"
)
);
first.start();
second.start();
}
static void print(
String name
) {
for (
int i = 1;
i <= 5;
i++
) {
System.out.println(
name
+ " "
+ i
);
}
}
}
Output order fixed নয়।
Possible:
First 1
First 2
Second 1
First 3
Second 2
...
Another run-এ order different হতে পারে।
Why Is Order Different?
Operating System এবং runtime decide করে:
কোন thread কখন CPU time পাবে।
আপনি সাধারণত assume করতে পারবেন না:
Thread A আগে start করেছি
তাই A আগে finish করবে।
Concurrency
Concurrency মানে multiple tasks-এর execution সময়ের মধ্যে overlap করা।
Conceptually:
Task A starts
Task B starts
Task A progresses
Task B progresses
Task A finishes
Task B finishes
একটি CPU core থাকলেও scheduler tasks-এর মধ্যে switch করে concurrency তৈরি করতে পারে।
Parallelism
Parallelism মানে multiple tasks literally একই সময়ে execute হচ্ছে।
সাধারণত multiple CPU cores available থাকলে।
Conceptually:
Core 1 → Task A
Core 2 → Task B
same instant-এ কাজ হচ্ছে।
Concurrency vs Parallelism
Important distinction:
Concurrency
→ multiple tasks make progress during overlapping time
Parallelism
→ multiple tasks execute at the same physical time
Concurrency does not automatically require multiple cores।
Parallelism usually does।
Example
Suppose one chef:
boil water
while waiting
cut vegetables
then return to water
এটি concurrency-এর মতো।
Two chefs:
Chef A boils water
Chef B cuts vegetables
same time-এ।
এটি parallelism-এর মতো।
Concurrency Is About Coordination
Concurrency শুধু speed নয়।
অনেক ক্ষেত্রে concurrency-এর challenge:
coordination
shared state
ordering
visibility
correctness
Performance পরে।
Correctness first।
Shared State
Suppose:
class Counter {
int value;
}
And two threads same object use করছে।
Thread A
→ counter.value
Thread B
→ counter.value
এই Counter object:
shared state
Mutable State
State mutable যখন value change করা যায়।
Example:
counter.value++;
So:
shared mutable state
means:
একই mutable data
multiple threads access করছে
এটাই concurrency bugs-এর major source।
Race Condition
Race Condition হয় যখন result depend করে:
threads কোন order-এ operations execute করেছে
এবং সেই ordering properly controlled নয়।
Simple Counter Example
class Counter {
private int count;
void increment() {
count++;
}
int value() {
return count;
}
}
Suppose two threads each:
100000 times increment()
call করে।
Expected:
200000
কিন্তু thread-safe না হলে result lower হতে পারে।
Why Can count++ Fail?
এটি দেখতে একটি operation:
count++;
কিন্তু conceptually multiple steps:
1. Read count
2. Add 1
3. Write count back
Suppose count:
10
Thread A:
read 10
Thread B:
read 10
Thread A:
write 11
Thread B:
write 11
Expected after two increments:
12
Actual:
11
একটি update হারিয়ে গেছে।
এটিকে বলা হয়:
Lost Update
Atomic Operation
An operation is atomic when it behaves conceptually as one indivisible action।
Meaning:
another thread cannot observe or interfere
halfway through that operation
count++ is not an atomic increment of a shared int।
Thread Safety
Code বা object:
thread-safe
যদি multiple threads concurrently use করলেও তার correctness guarantees maintain হয়।
Thread safety depends on design।
শুধু:
method ছোট
হলেই thread-safe হয় না।
Thread-Safe Counter with synchronized
class Counter {
private int count;
synchronized void increment() {
count++;
}
synchronized int value() {
return count;
}
}
Now increment() একই object-এর জন্য একবারে একটি thread execute করতে পারবে।
What Does synchronized Do?
High level-এ synchronized:
mutual exclusion
+
visibility guarantees
provide করে।
Mutual exclusion means:
একটি protected critical section
একই lock-এর জন্য
এক সময়ে একটি thread execute করে।
Critical Section
Code-এর যে অংশ shared mutable state access বা modify করে এবং concurrent interference থেকে protect করা দরকার:
critical section
Example:
synchronized void increment() {
count++;
}
Critical section:
count++;
Synchronized Block
পুরো method synchronize করার প্রয়োজন সবসময় নেই।
void increment() {
synchronized (
this
) {
count++;
}
}
This also synchronizes on:
this
Lock Mental Model
Conceptually:
Thread A acquires lock
↓
Thread A critical section execute করে
↓
Thread A releases lock
↓
Thread B lock পায়
If Thread B আসে lock occupied থাকা অবস্থায়:
wait করতে হবে
Monitor / Intrinsic Lock
Every Java object conceptually synchronization-এর জন্য একটি intrinsic lock বা monitor-এর সাথে associated হতে পারে।
When:
synchronized (
someObject
)
use করি, সেই object-এর monitor/lock ব্যবহার করি।
Foundation level-এ এটুকু mental model যথেষ্ট।
Same Lock Matters
Suppose:
synchronized (
lockA
) {
shared++;
}
and another method:
synchronized (
lockB
) {
shared++;
}
If:
lockA != lockB
তাহলে দুইটি block একে অপরকে mutually exclude করে না।
Shared state protect করতে synchronization strategy consistent হতে হবে।
Private Lock Object
Sometimes:
private final Object lock =
new Object();
Then:
void increment() {
synchronized (
lock
) {
count++;
}
}
এটি synchronization mechanism encapsulate করতে সাহায্য করে।
Synchronize State Invariants, Not Random Lines
Suppose:
class Wallet {
private long balance;
}
Withdraw:
boolean withdraw(
long amount
) {
if (
balance >= amount
) {
balance -=
amount;
return true;
}
return false;
}
This has a race।
Check-Then-Act Race
The operation is:
Check:
balance >= amount
Then:
subtract amount
Suppose balance:
100
Two threads withdraw:
80
Thread A sees:
100 >= 80
Thread B also sees:
100 >= 80
Both subtract।
Now state can become invalid।
Synchronize the Whole Compound Operation
synchronized boolean withdraw(
long amount
) {
if (
balance < amount
) {
return false;
}
balance -=
amount;
return true;
}
Important:
check + update
must be protected as one logical operation।
Compound Operations
A compound operation contains multiple steps that must behave atomically relative to other threads।
Examples:
check then act
read modify write
get then remove
if absent then insert
Concurrency reasoning must protect the entire invariant, not just one field write।
Example — Duplicate Registration
Bad pattern:
if (
!users.containsKey(
email
)
) {
users.put(
email,
user
);
}
If multiple threads execute concurrently:
both may observe missing
both may insert
Even if individual operations are safe in some collection, compound semantics need correct API or synchronization।
Later concurrent collections provide atomic methods for these patterns।
Visibility
Concurrency problem শুধু two threads একই সময়ে write করছে এতটুকু নয়।
আরেকটি issue:
এক thread-এর write
অন্য thread কখন দেখতে পাবে?
এটিকে বলা হয়:
visibility
Simple Visibility Example
class Worker {
private boolean running =
true;
void stop() {
running =
false;
}
void run() {
while (
running
) {
// work
}
}
}
One thread:
run()
আরেকটি thread:
stop()
call করে।
Naively আমরা expect করি loop stop করবে।
কিন্তু proper synchronization/visibility guarantee ছাড়া compiler/runtime/CPU behavior-এর কারণে worker thread updated value promptly observe করার guarantee নেই।
Java Memory Visibility
Modern processors এবং compilers performance-এর জন্য:
caching
reordering
register usage
optimizations
করতে পারে।
Concurrency-এর জন্য Java defines a memory model describing when writes become reliably visible between threads।
Foundation level-এ key lesson:
Shared mutable state access-এর জন্য
visibility rules ignore করা যাবে না।
volatile
Simple visibility use case-এর জন্য Java provides:
volatile
Example:
class Worker {
private volatile boolean running =
true;
void stop() {
running =
false;
}
void run() {
while (
running
) {
// work
}
}
}
Now writes to:
running
have visibility semantics appropriate for this shared flag pattern।
What volatile Is Good For
volatile useful হতে পারে যখন shared variable:
one independently readable/writable state value
এবং operation does not require compound atomicity।
Examples:
stop flag
configuration reference
latest immutable snapshot reference
depending on design।
volatile Does Not Make count++ Atomic
Important:
volatile int count;
Then:
count++;
still conceptually:
read
add
write
Multiple threads still race।
So:
volatile
≠ general-purpose thread safety
Wrong Counter
class Counter {
private volatile int count;
void increment() {
count++;
}
}
Still not thread-safe।
Why?
volatile improves visibility of reads/writes।
It does not combine:
read + modify + write
into one atomic increment।
synchronized vs volatile
Very simplified mental model:
synchronized
→ protect compound/shared critical sections
→ mutual exclusion + visibility
volatile
→ visibility for a shared variable
→ no mutual exclusion for compound logic
This is not the full Java Memory Model, but it is a useful foundation।
Immutability and Thread Safety
One of the easiest concurrency strategies:
Do not mutate shared data.
Example:
record CourseSummary(
String code,
String title
) {
}
If all components are immutable, multiple threads can read the same object safely without synchronization for mutation because there is no mutation।
Immutable Objects Simplify Concurrency
Mutable object:
Who can change it?
When?
While someone else reads?
Do I need a lock?
Immutable object:
Construct once
Then read
Much easier to reason about।
But Remember Shallow Immutability
Record:
record Data(
List<String> values
) {
}
with mutable list may still expose shared mutation।
Better:
record Data(
List<String> values
) {
Data {
values =
List.copyOf(
values
);
}
}
for stable immutable collection structure।
Stateless Code
Another strong thread-safety strategy:
Do not keep shared mutable fields.
Example:
final class PriceCalculator {
long calculate(
long basePrice,
long discount
) {
return basePrice
- discount;
}
}
No mutable instance fields।
Multiple threads can call:
calculate(...)
without interfering with each other।
Stateless Services
A service can be largely stateless:
final class CourseValidator {
boolean isValid(
Course course
) {
...
}
}
Inputs come through parameters।
Temporary values stay local।
No shared mutable request-specific fields।
This is a common reason stateless service designs scale well under concurrency।
Local Variables and Thread Safety
Method-local variables are generally confined to that invocation।
Example:
int total =
0;
inside a method call।
Another thread calling same method has its own local total।
This reduces interference।
Thread Confinement
If mutable data is used by only one thread and never shared, synchronization may not be needed।
This is called:
thread confinement
Example:
List<String> localResult =
new ArrayList<>();
created, used, and discarded entirely within one thread's task।
The Problem Starts When Reference Escapes
Suppose local mutable object is returned or placed into shared state:
sharedCache.put(
key,
localResult
);
Now other threads may access it।
It is no longer thread-confined।
Prefer Local State Over Shared Fields
Bad:
class CourseProcessor {
private final List<String> currentTitles =
new ArrayList<>();
List<String> process(
List<Course> courses
) {
currentTitles.clear();
for (
Course course
: courses
) {
currentTitles.add(
course.title()
);
}
return currentTitles;
}
}
If same service instance used concurrently, requests interfere।
Better
class CourseProcessor {
List<String> process(
List<Course> courses
) {
List<String> titles =
new ArrayList<>();
for (
Course course
: courses
) {
titles.add(
course.title()
);
}
return List.copyOf(
titles
);
}
}
Now request-specific mutable state is local।
Shared Mutable Singleton-Style State Is Dangerous
Backend frameworks often reuse service objects across requests।
Therefore avoid fields like:
private String currentUser;
private List<Course> currentCourses;
private int currentRequestCount;
unless synchronization/concurrency semantics intentionally designed।
Safe Publication — Basic Idea
Constructing an object is not the end of concurrency reasoning।
Other threads need to receive the reference through a safe mechanism if visibility matters।
Java concurrency utilities, synchronization, volatile references, and properly constructed immutable objects can participate in safe publication patterns।
We will not go deeply into Java Memory Model in this foundation course।
Key lesson:
How an object becomes shared also matters.
Thread Interleaving
Suppose:
System.out.println(
"A1"
);
System.out.println(
"A2"
);
in Thread A।
And:
System.out.println(
"B1"
);
System.out.println(
"B2"
);
in Thread B।
You may see:
A1
B1
B2
A2
Threads can interleave।
Never assume cross-thread sequence without explicit coordination।
Thread.sleep() Is Not Synchronization
A common beginner mistake:
Thread.sleep(
100
);
and assume:
অন্য thread নিশ্চয় finish করেছে।
This is not a correctness guarantee।
Sleep only delays a thread approximately according to scheduling semantics।
It does not establish the business condition you need।
Waiting for a Thread with join()
If one thread must wait until another finishes:
worker.start();
worker.join();
After successful join() completion, current thread continues after worker terminates।
InterruptedException
join() can require handling:
InterruptedException
Example:
try {
worker.join();
} catch (
InterruptedException exception
) {
Thread.currentThread()
.interrupt();
throw new IllegalStateException(
"Interrupted while waiting.",
exception
);
}
Why Restore Interrupt Status?
This pattern:
Thread.currentThread()
.interrupt();
preserves interruption signal for higher-level code after catching InterruptedException when you're not fully handling the cancellation yourself।
We will not go deep into interruption policy here, but silently ignoring interruption is usually a bad habit।
Complete Race Condition Example
public class Main {
public static void main(
String[] args
) throws InterruptedException {
Counter counter =
new Counter();
Thread first =
new Thread(
() ->
incrementManyTimes(
counter
)
);
Thread second =
new Thread(
() ->
incrementManyTimes(
counter
)
);
first.start();
second.start();
first.join();
second.join();
System.out.println(
counter.value()
);
}
static void incrementManyTimes(
Counter counter
) {
for (
int i = 0;
i < 100_000;
i++
) {
counter.increment();
}
}
static final class Counter {
private int count;
void increment() {
count++;
}
int value() {
return count;
}
}
}
Expected:
200000
But result may be lower due to race condition।
Thread-Safe Version
public class Main {
public static void main(
String[] args
) throws InterruptedException {
Counter counter =
new Counter();
Thread first =
new Thread(
() ->
incrementManyTimes(
counter
)
);
Thread second =
new Thread(
() ->
incrementManyTimes(
counter
)
);
first.start();
second.start();
first.join();
second.join();
System.out.println(
counter.value()
);
}
static void incrementManyTimes(
Counter counter
) {
for (
int i = 0;
i < 100_000;
i++
) {
counter.increment();
}
}
static final class Counter {
private int count;
synchronized void increment() {
count++;
}
synchronized int value() {
return count;
}
}
}
Now shared counter access protected।
Why Synchronize value() Too?
Suppose writes synchronized but reads unsynchronized।
Then visibility/coordination contract becomes inconsistent।
If field access is guarded by one lock, a strong design rule is:
all access to that guarded mutable state
uses the same synchronization strategy.
Guarded-by Mental Model
Think:
count
is guarded by
this object's lock
Then every:
read
write
compound operation
for count goes through that lock।
Consistency makes thread-safety reasoning much easier।
Synchronization Has Cost
Locks are not free।
Possible costs:
contention
waiting
context switching
reduced parallelism
But correctness comes first।
Do not remove synchronization just because:
locks are slow
without measurement and a safe alternative।
Coarse-Grained vs Fine-Grained Locking
High level:
Coarse-grained
→ larger region under one lock
→ simpler correctness
→ potentially more contention
Fine-grained
→ smaller/multiple locks
→ potentially more concurrency
→ much harder reasoning
Foundation code should generally prefer:
simple correctness
over clever locking।
Deadlock — Basic Preview
Suppose Thread A holds Lock 1 and waits for Lock 2।
Thread B holds Lock 2 and waits for Lock 1।
Now neither can proceed।
Thread A:
has A
waits B
Thread B:
has B
waits A
This is:
Deadlock
We will not teach advanced deadlock analysis here।
Important lesson:
multiple locks increase coordination complexity.
Why Concurrency Bugs Are Difficult
Sequential bug:
same input
→ often same failure
Concurrency bug may depend on timing:
Thread A happened here
exactly when
Thread B happened there
Tiny scheduling differences can change outcome।
Heisenbug-Like Behavior
Sometimes adding:
System.out.println(...)
changes timing enough that bug disappears।
This makes concurrency bugs particularly frustrating।
Never Reason from "It Worked 100 Times"
This is not proof:
I ran it many times
and result looked correct.
Race condition can remain latent।
Thread safety must come from:
design
happens-before relationships
synchronization
safe concurrent APIs
immutability
not luck।
Shared Collections
A normal:
ArrayList
HashMap
HashSet
should not automatically be assumed safe for arbitrary concurrent mutation।
Later আমরা শিখব:
ConcurrentHashMap
CopyOnWriteArrayList
BlockingQueue
and other concurrency tools।
Collections.synchronizedList()
Java also provides synchronized wrappers।
Example:
List<String> values =
Collections.synchronizedList(
new ArrayList<>()
);
But even synchronized collections do not make every multi-step compound operation automatically correct।
Example:
if (
!values.contains(
value
)
) {
values.add(
value
);
}
Still requires reasoning about the operation as a whole।
Thread-Safe Component ≠ Thread-Safe Workflow
Suppose every individual repository call is thread-safe।
But business operation:
check inventory
then reserve inventory
spans multiple calls।
The overall workflow can still race।
Thread safety must protect:
business invariant
not merely each method individually।
Example Business Invariant
Requirement:
Course capacity = 1
Only one learner may enroll
Bad:
check remaining seats
then create enrollment
two concurrent requests may both pass the check।
This becomes a wider consistency problem that may require:
atomic operation
transaction
database constraint
lock
compare-and-set
depending on architecture।
In this foundation course, key lesson:
Concurrency correctness often lives at invariant boundaries.
In-Memory Lock Does Not Protect Other Processes
Very important backend concept।
Suppose:
synchronized
protects data in one JVM।
If application runs:
Instance A
Instance B
Instance C
then each JVM has its own lock।
A lock in Instance A does not automatically lock Instance B।
Distributed Systems Boundary
Therefore:
synchronized
solves:
threads sharing one JVM object's lock
It does not solve:
cross-process
cross-server
distributed consistency
Those need different mechanisms।
We do not go deeper in this foundation course।
Thread Safety by Avoiding Sharing
Often best concurrency solution:
Do not share mutable state in the first place.
Strategies:
immutable objects
local variables
message passing
copying
stateless services
thread confinement
These can be safer than locking everything।
Example — Immutable Configuration
record AppConfig(
String environment,
int timeoutSeconds
) {
}
Create once।
Then all threads read।
No mutation।
This is much simpler than a mutable configuration object with synchronized getters/setters।
Volatile Immutable Snapshot Pattern
High-level example:
class ConfigHolder {
private volatile AppConfig current;
AppConfig current() {
return current;
}
void update(
AppConfig config
) {
current =
config;
}
}
If AppConfig itself is immutable, replacing one reference can be simpler than mutating many fields individually।
Do not generalize this pattern without understanding update consistency requirements, but it illustrates why immutable snapshots combine well with visibility mechanisms।
Multiple Mutable Fields Need One Invariant
Suppose:
class Range {
private int min;
private int max;
}
Invariant:
min <= max
Updating them independently with separate volatile fields does not automatically preserve a consistent pair for readers।
Sometimes multiple fields must be protected together as one state transition।
Atomicity Is About Logical State
Concurrency design should ask:
What must appear to happen together?
Maybe:
balance check + deduction
or:
min + max update
or:
check absent + insert
That logical operation should determine synchronization boundary।
Thread Safety Documentation
A class should have a clear concurrency policy।
Examples:
Immutable and thread-safe
Not thread-safe; caller must confine instance
All state guarded by private lock
Designed for concurrent access
Ambiguous ownership creates bugs।
Avoid Exposing Lock Objects
If synchronization uses:
private final Object lock =
new Object();
keeping it private prevents external code from unexpectedly participating in your lock protocol।
Encapsulation applies to concurrency mechanisms too।
Avoid Synchronizing on Mutable Public Values
Avoid designs like:
synchronized (
somePublicString
)
or publicly shared objects you do not control।
Use a dedicated private lock when appropriate।
Thread-Safe Does Not Mean Fast
A design can be:
correct but slow
or:
fast but incorrect
First achieve correctness।
Then measure contention and optimize with better primitives if necessary।
Thread Safety and APIs
Method signatures alone may not communicate thread-safety fully।
Example:
void add(
Course course
);
You still need documentation/design context to know:
Can multiple threads call it?
Does it synchronize?
Does caller own synchronization?
Concurrency and Testing
Concurrency tests can increase confidence but cannot replace reasoning।
Useful approaches may include:
repeated execution
stress tests
many tasks
controlled synchronization
invariant checks
But:
No failure observed
does not prove absence of a race।
Common Mistake 1 — Thinking count++ Is Atomic
It is a read-modify-write operation।
Multiple threads can lose updates।
Common Mistake 2 — Using volatile as a Lock
volatile int count;
does not make:
count++;
safe।
Common Mistake 3 — Synchronizing Only Half the Invariant
Wrong:
synchronized boolean hasBalance(
long amount
) {
return balance >=
amount;
}
void deduct(
long amount
) {
balance -=
amount;
}
The check এবং deduction together form the operation।
Common Mistake 4 — Different Locks for Same State
If readers/writers use different unrelated locks, mutual exclusion may fail।
Common Mistake 5 — Using sleep() for Coordination
Thread.sleep(...)
is not a correctness guarantee that another task finished।
Common Mistake 6 — Sharing Request-Specific Fields
Backend singleton-style service:
private User currentUser;
can cause concurrent requests to overwrite each other।
Keep request-specific state local।
Common Mistake 7 — Assuming Immutable Reference Means Immutable Object
final List<String> values
means reference cannot be reassigned।
List contents may still mutate।
Common Mistake 8 — Overusing Locks
Lock every method without understanding shared invariants can create unnecessary contention and even deadlock risk।
Prefer simple, explicit concurrency boundaries।
Common Mistake 9 — Assuming Thread Safety Solves Distributed Concurrency
A JVM lock protects threads within that lock domain।
It does not coordinate multiple application instances।
Common Mistake 10 — Swallowing InterruptedException
Avoid:
catch (
InterruptedException exception
) {
}
Interruption often represents cancellation/control signal।
Handle it deliberately।
Practical Example — Unsafe Inventory
final class Inventory {
private int available =
1;
boolean reserve() {
if (
available == 0
) {
return false;
}
available--;
return true;
}
}
Two threads can both see:
available == 1
and both reserve।
Thread-Safe In-Memory Version
final class Inventory {
private int available =
1;
synchronized boolean reserve() {
if (
available == 0
) {
return false;
}
available--;
return true;
}
synchronized int available() {
return available;
}
}
Now:
check + decrement
protected together।
Practical Example — Stop Flag
final class Worker {
private volatile boolean running =
true;
void stop() {
running =
false;
}
void run() {
while (
running
) {
doWork();
}
}
private void doWork() {
// Small unit of work
}
}
This illustrates visibility-oriented use of volatile।
Do Not Extend This Example Blindly
Real worker cancellation may involve:
blocking I/O
interrupts
executors
resource cleanup
A boolean flag alone may not be enough।
This example only demonstrates visibility semantics।
Practical Example — Stateless Service
final class DiscountCalculator {
long discountedPrice(
long price,
int percentage
) {
if (
percentage < 0
|| percentage > 100
) {
throw new IllegalArgumentException(
"Invalid percentage."
);
}
long discount =
price
* percentage
/ 100;
return price
- discount;
}
}
No shared mutable state।
Concurrent calls do not interfere through fields।
Practice 1 — Process or Thread?
A running Java application is primarily a:
Answer
Process
এর ভিতরে one or more:
Threads
থাকে।
Practice 2 — Concurrency or Parallelism?
One CPU core alternates between Task A and Task B।
Answer
Concurrency
There may be no physical simultaneous execution।
Practice 3
Two CPU cores execute A and B at the same instant।
Answer
Parallelism
Practice 4 — Shared State
Two threads access same mutable Counter object।
Is it shared mutable state?
Answer
Yes।
Practice 5 — count++
Is:
count++;
atomic for shared concurrency purposes?
Answer
No।
Conceptually:
read
modify
write
Practice 6 — Race Condition
Counter starts:
10
Two threads both read 10, increment, and write 11।
Expected:
12
Actual:
11
What happened?
Answer
Lost update caused by race condition.
Practice 7 — Synchronization Boundary
Which must be protected together?
check balance
then deduct balance
Answer
Both steps as one compound operation।
Practice 8 — Volatile
Does:
volatile int count;
make:
count++;
thread-safe?
Answer
No।
Practice 9 — Volatile Use Case
A simple shared stop flag where one thread writes and another observes।
Could volatile be appropriate?
Answer
Yes, as a basic visibility-oriented pattern, assuming no wider coordination requirement exists।
Practice 10 — Immutable Object
Multiple threads only read a deeply immutable value object।
Do they normally need synchronization just for those reads?
Answer
No mutation means read-only sharing is much easier to make safe।
Proper construction/publication still matters, but no lock is required merely to protect nonexistent mutation।
Practice 11 — Local State
Each method call creates its own:
ArrayList
and does not share it।
What strategy is this close to?
Answer
Thread confinement / local state.
Practice 12 — sleep()
Can:
Thread.sleep(
100
);
guarantee worker completed?
Answer
No।
Use an actual coordination mechanism।
Practice 13 — join()
What does:
worker.join();
conceptually do?
Answer
Current thread waits for worker to terminate, subject to interruption handling।
Practice 14 — Multiple Application Instances
Does:
synchronized
in JVM A automatically block the same method in JVM B?
Answer
No।
Their locks are in separate processes/JVMs।
Practice 15 — Safer Design
Which is easier to reason about concurrently?
Shared mutable object
Immutable object
Answer
Usually:
Immutable object
True or False
- A Java process may contain multiple threads.
- Concurrency always means two tasks are physically executing at the same instant.
- Parallelism usually involves actual simultaneous execution.
- Shared mutable state can create race conditions.
count++is an atomic shared-state increment.synchronizedcan provide mutual exclusion.- Compound operations may need one synchronization boundary.
volatilemakes every compound operation atomic.volatilecan help with visibility of a shared flag.- Immutable data is generally easier to share safely.
- Method-local request state is often safer than shared mutable fields.
Thread.sleep()is a reliable synchronization mechanism.join()can wait for another thread to finish.- A JVM lock automatically coordinates every server instance in a cluster.
- Thread-safe code should protect business invariants, not just isolated statements.
Answers
1. True
2. False
3. True
4. True
5. False
6. True
7. True
8. False
9. True
10. True
11. True
12. False
13. True
14. False
15. True
Knowledge Check
Question 1
Process এবং Thread-এর difference কী?
Question 2
Concurrency এবং Parallelism-এর difference কী?
Question 3
Shared mutable state কী?
Question 4
Race condition কী?
Question 5
কেন count++ thread-safe নয়?
Question 6
Atomicity বলতে কী বোঝায়?
Question 7
synchronized high level-এ কী provide করে?
Question 8
Critical section কী?
Question 9
Visibility problem কী?
Question 10
volatile এবং synchronized-এর basic difference কী?
Question 11
কেন immutability concurrency সহজ করে?
Question 12
Thread confinement কী?
Question 13
কেন check-then-act operation dangerous হতে পারে?
Question 14
কেন sleep() coordination mechanism নয়?
Question 15
কেন in-memory synchronized distributed lock নয়?
Knowledge Check Answers
Answer 1
Process হলো running program-এর execution environment।
একটি process-এর ভিতরে multiple Threads থাকতে পারে।
Thread হলো process-এর ভিতরে একটি execution path।
Answer 2
Concurrency-তে multiple tasks overlapping সময়ের মধ্যে progress করে।
Parallelism-এ multiple tasks একই physical instant-এ execute করতে পারে।
Answer 3
একই mutable object বা state multiple threads access করলে সেটি shared mutable state।
Answer 4
যখন program result uncontrolled thread interleaving/order-এর উপর depend করে এবং incorrect result possible হয়, সেটি race condition।
Answer 5
কারণ এটি এক conceptual machine-level atomic action নয়।
এতে:
read
increment
write
steps আছে।
Multiple threads interleave করে lost update করতে পারে।
Answer 6
একটি operation অন্য thread-এর দৃষ্টিতে indivisible logical action-এর মতো behave করলে সেটিকে atomic বলা যায়।
Answer 7
Foundation level-এ:
mutual exclusion
+
memory visibility guarantees
provide করে।
একই lock-এর protected code এক সময়ে একটি thread execute করে।
Answer 8
Shared mutable state-এর যে code region concurrent interference থেকে protect করা দরকার।
Answer 9
এক thread state update করলেও অন্য thread সেই updated value reliably observe করবে কি না সেই concern।
Answer 10
synchronized critical section-এর জন্য mutual exclusion এবং visibility provide করে।
volatile shared variable-এর visibility/order semantics provide করতে পারে, কিন্তু compound read-modify-write operation-এর জন্য mutual exclusion দেয় না।
Answer 11
কারণ state change হয় না।
তাই multiple threads-এর মধ্যে:
who writes
when writes
partial update
lost update
এসব coordination problem থাকে না।
Answer 12
Mutable state শুধু একটি thread-এর মধ্যে সীমাবদ্ধ রাখা এবং অন্য threads-এর সাথে share না করা।
Answer 13
কারণ check এবং action-এর মাঝখানে অন্য thread state change করতে পারে।
তাই:
check
+
act
এক logical atomic operation হিসেবে protect করতে হতে পারে।
Answer 14
Sleep শুধু current thread delay করে।
এটি অন্য thread-এর completion বা required state change-এর guarantee দেয় না।
Answer 15
synchronized একটি specific JVM-এর object/monitor lock-এর মধ্যে কাজ করে।
Different JVM/process-এর memory এবং locks আলাদা।
Practical Thread-Safety Checklist
Shared state দেখলে প্রশ্ন করুন:
এই state mutable কি?
কতগুলো thread access করতে পারে?
একাধিক thread write করতে পারে কি?
Read এবং write-এর visibility guarantee কী?
কোন operations logically atomic হওয়া দরকার?
একটি shared invariant আছে কি?
সব accesses একই lock strategy use করছে কি?
এই state local করা যায় কি?
Immutable করা যায় কি?
Concurrent collection ব্যবহার করা উচিত কি?
এই lock কি শুধু one JVM protect করছে?
Concurrency Design Preference
Possible হলে এই order-এ ভাবুন:
1. Avoid shared mutable state
2. Prefer immutable data
3. Keep request/task state local
4. Use thread-safe abstractions
5. Synchronize clear invariants when needed
6. Measure before optimizing lock strategy
Core Mental Model
Concurrency problem দেখলে শুধু ভাববেন না:
Two threads একই line execute করছে কি?
Better question:
What shared state exists?
What invariant must remain true?
Which operations must appear atomic?
How do threads see each other's updates?
Example:
if (
balance >= amount
) {
balance -= amount;
}
The important unit is not:
balance -= amount
alone।
The logical operation is:
Check sufficient balance
+
Deduct balance
Concurrency safety must protect that whole decision।
Lesson Summary
এই lesson-এ আমরা Java concurrency এবং thread safety-এর foundation শিখেছি।
আমরা শিখেছি:
- একটি process-এর ভিতরে multiple threads থাকতে পারে
- Thread একটি execution path
- Concurrency এবং Parallelism একই concept নয়
- Concurrent tasks-এর execution interleave করতে পারে
- Execution order সাধারণত assume করা যায় না
- Shared mutable state concurrency bugs-এর major source
- Race condition thread scheduling/interleaving-এর উপর incorrect result তৈরি করতে পারে
count++atomic নয়- Lost update একটি common race condition
- Thread-safe code concurrent access-এর মধ্যেও correctness maintain করে
synchronizedmutual exclusion এবং visibility provide করতে পারে- Critical section shared state-এর protected operation
- একই shared state-এর জন্য consistent locking গুরুত্বপূর্ণ
- Compound operations যেমন check-then-act একসঙ্গে protect করতে হয়
- Visibility concurrency-এর আলাদা concern
volatilesimple shared-state visibility use cases-এ usefulvolatilecompound operations atomic করে না- Immutability thread-safety reasoning অনেক সহজ করে
- Stateless design shared mutable state কমায়
- Method-local state thread confinement তৈরি করতে পারে
- Mutable state shared হলে synchronization strategy প্রয়োজন হতে পারে
Thread.sleep()coordination guarantee নয়join()thread completion-এর জন্য wait করতে পারে- Interruption deliberately handle করা উচিত
- Concurrency bugs timing-dependent হওয়ায় difficult to reproduce
- Thread-safe component থাকলেও overall workflow race করতে পারে
- Business invariant concurrency boundary determine করতে পারে
synchronizedone JVM-এর threads coordinate করে, distributed systems নয়- Simpler concurrency design সাধারণত clever locking-এর চেয়ে safer
সবচেয়ে important principle:
Concurrency-এর সবচেয়ে সহজ bug fix
অনেক সময় lock যোগ করা নয়।
Shared mutable state কমানো।
আর concurrency reasoning-এর core question:
What state is shared,
what can change,
and what must remain true
while multiple tasks execute?
Next Lesson
পরবর্তী lesson:
Executors, Callable, Future, and CompletableFuture
আমরা শিখব:
- Raw Thread তৈরি করার limitation
- Task এবং Thread-এর difference
ExecutorService- Thread pools
execute()submit()RunnableCallable<T>Future<T>- Waiting for results
- Exceptions from asynchronous tasks
- Executor shutdown
CompletableFuturesupplyAsync()runAsync()thenApply()thenAccept()thenCompose()thenCombine()- Error handling
- Blocking বনাম asynchronous composition
- Common executor mistakes