Modern Java
Virtual Threads and Concurrent Collections
আপনি একটি free preview lesson দেখছেন।
Lesson Overview
আগের lessons-এ আমরা Java concurrency-এর foundation শিখেছি:
Thread
Runnable
Callable
ExecutorService
Future
CompletableFuture
synchronized
volatile
Thread Safety
Traditional Java concurrency-তে Platform Thread তুলনামূলক expensive resource।
তাই সাধারণত limited number of threads নিয়ে:
Executors.newFixedThreadPool(...)
ব্যবহার করা হয়।
Modern Java আরেক ধরনের Thread provide করে:
Virtual Thread
Virtual Thread বিশেষভাবে useful যখন application-এ অনেক concurrent task থাকে এবং সেই tasks-এর বড় অংশ সময় কাটে:
waiting
blocking
I/O-এর জন্য অপেক্ষা
এই lesson-এর দ্বিতীয় অংশে আমরা শিখব concurrent collections:
ConcurrentHashMap
CopyOnWriteArrayList
BlockingQueue
কারণ multiple threads একই collection access করলে normal collections সবসময় safe নয়।
এই lesson-এ আমরা শিখব:
- Platform Thread এবং Virtual Thread
- Virtual Thread কেন useful
- Virtual Thread কোথায় useful নয়
Thread.startVirtualThread()Thread.ofVirtual()Executors.newVirtualThreadPerTaskExecutor()- Task-per-thread model
- কেন Virtual Threads pool করা হয় না
- Resource concurrency limit
Semaphore- Virtual Threads এবং thread safety
ConcurrentHashMapputIfAbsent()computeIfAbsent()CopyOnWriteArrayListBlockingQueueArrayBlockingQueue- Producer-consumer pattern
- Backpressure
- Thread-safe collection বনাম thread-safe workflow
Platform Thread
Traditional Java Thread:
Thread worker =
new Thread(
() ->
doWork()
);
একটি:
Platform Thread
create করে।
Platform Thread operating system thread-এর সাথে closely connected।
Conceptually:
Java Platform Thread
↓
Operating System Thread
↓
CPU
Why Platform Threads Are Limited
Operating System threads resource consume করে।
Examples:
memory
thread stack
OS scheduling
context switching
runtime bookkeeping
তাই application-এ arbitrary সংখ্যক Platform Threads create করা practical নয়।
Traditional Thread Pool
এই কারণে traditional concurrency-তে:
ExecutorService executor =
Executors.newFixedThreadPool(
10
);
ব্যবহার করা হয়।
Conceptually:
1000 Tasks
↓
Task Queue
↓
10 Platform Threads
একটি worker task শেষ করলে পরের task নেয়।
Why Pool Platform Threads?
Mental model:
Platform Threads expensive
↓
few threads create করো
↓
reuse করো
এটি Platform Thread-এর জন্য sensible strategy।
What Is a Virtual Thread?
Virtual Thread-ও Java:
Thread
।
কিন্তু Virtual Thread পুরো lifetime একটি নির্দিষ্ট Operating System Thread ধরে রাখে না।
Java runtime Virtual Threads schedule করতে পারে।
Conceptually:
Many Virtual Threads
↓
Java Runtime
↓
Carrier Platform Threads
↓
Operating System
The Important Difference
Suppose একটি task:
কিছু Java code execute করল
↓
I/O-এর জন্য wait করল
↓
response এলো
↓
আবার Java code execute করল
Platform Thread waiting-এর সময় underlying OS Thread occupy করতে পারে।
Virtual Thread-এর ক্ষেত্রে runtime অনেক blocking situation-এ Virtual Thread suspend করে underlying execution resource অন্য task-এর জন্য ব্যবহার করতে পারে।
এটাই high-concurrency blocking workload-এর জন্য Virtual Threads powerful করে।
Virtual Threads Are Not Faster Threads
এই distinction খুব important।
Virtual Thread:
একটি CPU calculation দ্রুত করে না।
Suppose একটি calculation-এর CPU work:
500 ms
Virtual Thread ব্যবহার করলেই সেটি:
50 ms
হয়ে যাবে না।
Virtual Thread-এর goal:
Scale
not:
Make one task faster
Throughput vs Latency
Two different concepts:
Throughput
→ একটি সময়ের মধ্যে কত কাজ handle করা যাচ্ছে
Latency
→ একটি individual কাজ শেষ হতে কত সময় লাগছে
Virtual Threads অনেক waiting-heavy task concurrently handle করতে সাহায্য করতে পারে।
এটি individual operation-এর latency automatically কমায় না।
Good Virtual Thread Workloads
Virtual Threads strong candidate যখন tasks:
high in number
mostly independent
frequently blocking
spend significant time waiting
Examples conceptually:
network I/O
file I/O
many request-style operations
blocking queue operations
external service waiting
CPU-Bound Work
Examples:
large mathematical calculation
compression
image transformation
large parsing workload
cryptographic computation
এগুলো CPU-bound হতে পারে।
If machine has:
8 CPU cores
Virtual Threads:
10,000 CPU cores
create করে না।
CPU-bound workload-এর parallelism এখনও hardware capacity দ্বারা limited।
Creating a Virtual Thread
Simple API:
Thread worker =
Thread.startVirtualThread(
() ->
System.out.println(
"Running in a virtual thread"
)
);
এটি Virtual Thread create এবং start করে।
Check Thread Type
System.out.println(
worker.isVirtual()
);
Result:
true
Virtual Thread Builder
Another API:
Thread worker =
Thread.ofVirtual()
.start(
() ->
doWork()
);
Named Virtual Thread
Thread worker =
Thread.ofVirtual()
.name(
"course-worker"
)
.start(
() ->
doWork()
);
Thread names debugging এবং observability-তে useful।
Virtual-Thread-Per-Task Executor
Application code-এ manually every Thread create না করে Executor ব্যবহার করা যায়:
ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor();
প্রতিটি submitted task-এর জন্য একটি নতুন Virtual Thread তৈরি হয়।
Conceptually:
Task 1
→ Virtual Thread 1
Task 2
→ Virtual Thread 2
Task 3
→ Virtual Thread 3
Task-Per-Thread Model
Platform Thread pool mental model:
Threads are workers.
Tasks borrow workers.
Virtual Thread mental model:
One task
≈
One Virtual Thread
Virtual Thread task-এর lifetime represent করতে পারে।
Example
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(
String[] args
) throws Exception {
try (
var executor =
Executors
.newVirtualThreadPerTaskExecutor()
) {
Future<String> java =
executor.submit(
() ->
loadCourse(
"JAVA"
)
);
Future<String> backend =
executor.submit(
() ->
loadCourse(
"BACKEND"
)
);
System.out.println(
java.get()
);
System.out.println(
backend.get()
);
}
}
static String loadCourse(
String code
) {
return "Loaded "
+ code;
}
}
Why Try-With-Resources?
ExecutorService resource lifecycle manage করতে হয়।
Modern Java-তে:
try (
var executor =
Executors
.newVirtualThreadPerTaskExecutor()
) {
...
}
executor lifecycle block-এর সাথে clearly bound করা যায়।
Do Not Pool Virtual Threads
Traditional logic:
Platform Thread expensive
→ pool them
Virtual Thread logic:
Virtual Thread lightweight
→ create one per task
তাই:
fixed pool of 20 Virtual Threads
create করা সাধারণত Virtual Thread model-এর উদ্দেশ্য নয়।
But Does This Mean Unlimited Work?
No।
এটি important।
Virtual Threads cheap হওয়া মানে system-এর সব resource unlimited নয়।
Still limited:
CPU
memory
database connections
network connections
file descriptors
downstream service capacity
queue capacity
So:
many Virtual Threads allowed
does not mean:
unlimited work allowed
Thread Limit vs Resource Limit
Suppose:
5000 Virtual Threads
একটি downstream resource call করছে।
কিন্তু সেই resource safely handle করতে পারে:
20 concurrent operations
আমাদের limit করা উচিত:
resource access
not Virtual Thread creation itself।
Semaphore
Java provides:
Semaphore
যেটি concurrent access limit করতে পারে।
Example:
Semaphore permits =
new Semaphore(
20
);
Meaning:
maximum 20 tasks
protected section-এ একই সময়ে ঢুকতে পারবে।
Semaphore Example
import java.util.concurrent.Semaphore;
final class LimitedService {
private final Semaphore permits =
new Semaphore(
10
);
String call(
String value
) throws InterruptedException {
permits.acquire();
try {
return performCall(
value
);
} finally {
permits.release();
}
}
private String performCall(
String value
) {
return "Processed "
+ value;
}
}
Flow:
acquire permit
↓
perform limited operation
↓
release permit
Why finally Matters
If:
performCall(...)
throws exception, permit still release করতে হবে।
Therefore:
finally {
permits.release();
}
important।
Otherwise permits leak হতে পারে এবং eventually no task can proceed।
Virtual Thread + Semaphore
Many Virtual Threads থাকতে পারে:
Task 1
Task 2
...
Task 10000
but Semaphore limit করতে পারে:
Only 10 call this resource simultaneously
This cleanly separates:
Task concurrency
from:
Resource capacity
Virtual Threads Do Not Remove Race Conditions
Suppose:
int count;
Then many Virtual Threads:
count++;
execute করছে।
Same problem remains:
read
modify
write
count++ still not atomic।
Virtual Threads thread cost change করে।
তারা Java Memory Model change করে না।
Same Thread-Safety Rules Apply
Virtual Threads ব্যবহার করলেও:
shared mutable state
race conditions
visibility
atomicity
locks
immutability
সব concepts relevant থাকে।
Example:
ArrayList
Virtual Thread ব্যবহার করলেই concurrent collection হয়ে যায় না।
Virtual Threads and Synchronous Code
Virtual Threads-এর বড় advantage হলো waiting-heavy workflows simple blocking style-এ লেখা যায়।
Example:
Course course =
loadCourse();
List<Lesson> lessons =
loadLessons(
course
);
process(
lessons
);
এই synchronous structure অনেক সময়:
easy to read
easy to debug
easy to reason about
হতে পারে।
What About CompletableFuture?
Virtual Threads CompletableFuture obsolete করে না।
They solve different concerns।
CompletableFuture strong যখন workflow:
multiple async computations compose করা
independent results combine করা
completion pipeline বানানো
Virtual Threads strong যখন:
many blocking tasks
simple sequential code
task-per-thread model
Example Decision
Sequential dependency:
Load Course
↓
Load Lessons
↓
Generate Result
Virtual Thread-এর ভিতরে ordinary blocking code very readable হতে পারে।
Independent operations:
Load Course ─┐
├→ Combine
Load Stats ──┘
CompletableFuture.thenCombine() useful হতে পারে।
Use the abstraction that best communicates the workflow।
Concurrent Collections
Now suppose multiple threads genuinely একই collection share করছে।
Normal collections:
HashMap
ArrayList
HashSet
ArrayDeque
excellent general-purpose data structures।
কিন্তু arbitrary concurrent mutation-এর জন্য এগুলো automatically safe নয়।
Java তাই concurrency-aware collections provide করে।
Important Rule
Thread-safe collection
≠
Thread-safe business workflow
Collection-এর individual operations safe হতে পারে।
কিন্তু multiple operations combine করে business logic race করতে পারে।
ConcurrentHashMap
Concurrent key-value access-এর জন্য:
ConcurrentHashMap<K, V>
Example:
import java.util.concurrent.ConcurrentHashMap;
ConcurrentHashMap<String, Course> courses =
new ConcurrentHashMap<>();
Basic Operations
Insert:
courses.put(
course.code(),
course
);
Lookup:
Course course =
courses.get(
"JAVA"
);
Remove:
courses.remove(
"JAVA"
);
Check-Then-Act Problem
Suppose requirement:
Course code absent হলে insert করো।
Naive:
if (
!courses.containsKey(
course.code()
)
) {
courses.put(
course.code(),
course
);
}
Problem:
containsKey()
and:
put()
two separate operations।
Thread A এবং Thread B দুজনেই:
missing
observe করতে পারে before either inserts।
putIfAbsent()
Better:
Course existing =
courses.putIfAbsent(
course.code(),
course
);
This expresses:
insert only if missing
as one concurrent Map operation।
Registration Example
boolean register(
Course course
) {
Course existing =
courses.putIfAbsent(
course.code(),
course
);
return existing
== null;
}
If absent:
inserted
→ true
If already exists:
not replaced
→ false
computeIfAbsent()
Suppose missing value হলে create করতে হবে।
Example:
CourseStatistics statistics =
statisticsByCourse
.computeIfAbsent(
courseCode,
ignored ->
new CourseStatistics()
);
Meaning:
value আছে
→ existing return
value নেই
→ create and associate value
Why This Is Useful
Instead of:
CourseStatistics stats =
map.get(
code
);
if (
stats == null
) {
stats =
new CourseStatistics();
map.put(
code,
stats
);
}
use:
map.computeIfAbsent(
code,
ignored ->
new CourseStatistics()
);
when that matches your intent।
Keep Computation Small
Avoid huge operation inside:
computeIfAbsent(...)
Example of poor direction:
map.computeIfAbsent(
code,
key -> {
// external calls
// long calculations
// unrelated mutations
// many business operations
}
);
The computation should remain focused।
ConcurrentHashMap and null
ConcurrentHashMap does not use null as normal keys or values।
This helps methods such as:
get()
use:
null
to clearly mean:
no mapping
Thread-Safe Map Does Not Make Values Thread-Safe
Suppose:
ConcurrentHashMap<
String,
ArrayList<String>
> lessons =
new ConcurrentHashMap<>();
The outer Map is concurrency-aware।
But:
ArrayList<String>
values are still ordinary mutable Lists।
Two threads mutating the same List can still race।
Example
lessons.computeIfAbsent(
"JAVA",
ignored ->
new ArrayList<>()
).add(
"Streams"
);
The computeIfAbsent() operation is map-safe।
The subsequent:
add()
acts on a normal ArrayList।
Outer collection safety does not automatically propagate into nested objects।
Prefer Immutable Values Where Possible
Example:
ConcurrentHashMap<
String,
CourseSummary
> courses =
new ConcurrentHashMap<>();
If CourseSummary is deeply immutable:
concurrent reads become easier to reason about
because threads are not mutating shared CourseSummary state।
Atomic Map Operation Is Not a Transaction
Suppose business operation touches:
courses Map
+
enrollments Map
+
capacity
Using ConcurrentHashMap for each does not automatically make:
check capacity
+
create enrollment
+
update count
atomic।
The business invariant spans multiple pieces of state।
You still need an appropriate higher-level concurrency strategy।
CopyOnWriteArrayList
Java provides:
CopyOnWriteArrayList<E>
A concurrent List implementation।
Example:
CopyOnWriteArrayList<String> listeners =
new CopyOnWriteArrayList<>();
How Copy-on-Write Works
High-level mental model:
Read
→ existing snapshot
Write
→ underlying array copied
→ modification applied to new copy
Therefore writes are comparatively expensive।
Good Workload
CopyOnWriteArrayList useful when:
reads are very frequent
iteration is frequent
writes are rare
collection is relatively small
Example:
event listeners
callbacks
small configuration subscribers
Poor Workload
Avoid choosing it automatically for:
large write-heavy collection
because every mutation may involve copying array state।
Snapshot-Style Iteration
Suppose iterator create হলো।
Another thread পরে:
listeners.add(
"audit"
);
Existing iterator নতুন value নাও দেখতে পারে।
Iterator একটি stable snapshot-style view observe করে।
This is intentional।
Example
import java.util.concurrent.CopyOnWriteArrayList;
final class ListenerRegistry {
private final CopyOnWriteArrayList<String> listeners =
new CopyOnWriteArrayList<>();
void add(
String listener
) {
listeners.addIfAbsent(
listener
);
}
void notifyListeners() {
for (
String listener
: listeners
) {
System.out.println(
"Notify "
+ listener
);
}
}
}
Good fit when registration rare but notification traversal frequent।
BlockingQueue
Producer-consumer workflow-এর জন্য:
BlockingQueue<E>
একটি powerful abstraction।
It extends Queue semantics with operations that can:
wait for data
wait for capacity
Producer-Consumer Model
Conceptually:
Producer
↓
BlockingQueue
↓
Consumer
Producer work তৈরি করে।
Consumer work process করে।
Queue handoff coordinate করে।
Why Not Busy Poll?
Without blocking support, consumer may do:
while (
true
) {
Task task =
queue.poll();
if (
task == null
) {
continue;
}
process(
task
);
}
Queue empty হলে loop repeatedly execute হয়।
এটি unnecessary CPU consume করতে পারে।
take()
BlockingQueue:
Task task =
queue.take();
If queue empty:
wait
until element becomes available।
No busy polling needed।
put()
For bounded BlockingQueue:
queue.put(
task
);
If queue full:
wait
until space becomes available।
Main Operation Families
Immediate attempt:
offer(
value
)
Wait for space:
put(
value
)
Immediate removal:
poll()
Wait for data:
take()
Mental model:
offer()
→ try now
put()
→ wait for capacity
poll()
→ try now
take()
→ wait for data
ArrayBlockingQueue
A bounded FIFO BlockingQueue implementation:
BlockingQueue<String> queue =
new ArrayBlockingQueue<>(
100
);
Capacity:
100
Once queue is full:
put(...)
waits।
If queue empty:
take()
waits।
Backpressure
Suppose producer can generate:
10,000 tasks/second
but consumer can process:
1,000 tasks/second
If queue grows without meaningful limits:
queue grows
↓
memory usage grows
↓
latency grows
↓
system becomes unhealthy
A bounded queue can push pressure back to producer।
When full:
put()
must wait।
This is a simple form of:
backpressure
Complete Producer-Consumer Example
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class Main {
public static void main(
String[] args
) throws InterruptedException {
BlockingQueue<String> queue =
new ArrayBlockingQueue<>(
2
);
Thread producer =
Thread.startVirtualThread(
() ->
produce(
queue
)
);
Thread consumer =
Thread.startVirtualThread(
() ->
consume(
queue
)
);
producer.join();
consumer.join();
}
static void produce(
BlockingQueue<String> queue
) {
try {
for (
int i = 1;
i <= 5;
i++
) {
String task =
"Task-"
+ i;
queue.put(
task
);
System.out.println(
"Produced "
+ task
);
}
} catch (
InterruptedException exception
) {
Thread.currentThread()
.interrupt();
}
}
static void consume(
BlockingQueue<String> queue
) {
try {
for (
int i = 1;
i <= 5;
i++
) {
String task =
queue.take();
System.out.println(
"Consumed "
+ task
);
}
} catch (
InterruptedException exception
) {
Thread.currentThread()
.interrupt();
}
}
}
Why Capacity 2 Matters
Producer can add:
Task-1
Task-2
Queue becomes full।
Before another:
put(...)
can succeed, consumer needs to remove something।
This creates natural coordination।
BlockingQueue Does Not Accept null
BlockingQueue uses:
null
as a special absence result for methods such as:
poll()
Therefore normal queue elements cannot be null।
Do Not Check Before take()
Avoid:
if (
!queue.isEmpty()
) {
return queue.take();
}
The queue can change between:
isEmpty()
and:
take()
If desired semantics are:
wait for an item
simply use:
take()
Use Semantic Atomic Operations
Concurrency APIs often already provide an operation representing the whole intent।
Instead of:
if (
!map.containsKey(
key
)
) {
map.put(
key,
value
);
}
use:
map.putIfAbsent(
key,
value
);
Instead of:
if (
!queue.isEmpty()
) {
queue.poll();
}
just:
queue.poll();
if immediate attempt is what you mean।
Thread-Safe Collection vs Thread-Safe Workflow
Consider:
ConcurrentHashMap<String, Course> courses;
This operation:
courses.putIfAbsent(...)
has concurrent Map semantics।
But this workflow:
Check Course exists
↓
Check seat capacity
↓
Create Enrollment
↓
Update another structure
spans multiple pieces of state।
A concurrent Map cannot automatically make the whole workflow atomic।
Protect the Business Invariant
Concurrency reasoning should ask:
What must remain true?
For example:
Only 100 learners may enroll.
The invariant is not:
Map.put() must be thread-safe.
It is:
Enrollment count must never exceed 100.
The whole state transition needs a strategy capable of protecting that rule।
Tool-to-Problem Mapping
Virtual Thread
→ represent many concurrent tasks
ExecutorService
→ execute/manage tasks
Semaphore
→ limit access to scarce resource
ConcurrentHashMap
→ concurrent key-value operations
CopyOnWriteArrayList
→ read-heavy concurrent List
BlockingQueue
→ producer-consumer handoff
synchronized
→ protect explicit critical section
Immutability
→ reduce shared mutable state
Different tools solve different problems।
Common Mistake 1 — Virtual Threads Make Everything Faster
False।
They mainly help scalability for many waiting/blocking tasks।
Common Mistake 2 — Pooling Virtual Threads
Traditional Platform Thread pool patterns should not automatically be applied to Virtual Threads।
Prefer:
Executors.newVirtualThreadPerTaskExecutor()
for task-per-thread usage।
Common Mistake 3 — Unlimited Tasks
Virtual Threads are lightweight, but:
work
memory
CPU
connections
downstream capacity
remain finite।
Workload control still matters।
Common Mistake 4 — Virtual Threads Remove Race Conditions
They do not।
This remains unsafe:
count++;
when shared concurrently।
Common Mistake 5 — Using Thread Count to Protect a Resource
If database or external service capacity is:
20
model that resource limit explicitly, for example with:
Semaphore
when appropriate।
Common Mistake 6 — HashMap for Concurrent Mutation
If Map is deliberately shared across multiple writers, use appropriate concurrency control or a concurrent implementation।
Common Mistake 7 — Manual Check-Then-Act
Avoid:
containsKey()
then
put()
when:
putIfAbsent()
matches the requirement।
Common Mistake 8 — Expensive computeIfAbsent()
Keep mapping function:
short
focused
side-effect controlled
Do not hide a large business workflow inside it।
Common Mistake 9 — CopyOnWriteArrayList for Heavy Writes
It is designed around expensive writes and cheap/stable reads।
Write-heavy workloads need another strategy।
Common Mistake 10 — Busy Polling
Avoid repeatedly:
poll()
inside a tight empty loop when what you really want is:
take()
and wait।
Common Mistake 11 — Ignoring Interruption
Operations such as:
Semaphore.acquire()
BlockingQueue.put()
BlockingQueue.take()
may throw:
InterruptedException
Handle interruption deliberately।
Common Mistake 12 — Concurrent Outer Collection Means Everything Safe
A:
ConcurrentHashMap<String, ArrayList<String>>
still contains mutable ArrayList values।
Nested state requires its own thread-safety reasoning।
Practical Example — Concurrent Course Registry
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public final class CourseRegistry {
private final ConcurrentHashMap<String, Course> courses =
new ConcurrentHashMap<>();
public boolean register(
Course course
) {
Course existing =
courses.putIfAbsent(
course.code(),
course
);
return existing
== null;
}
public Optional<Course> find(
String code
) {
return Optional.ofNullable(
courses.get(
code
)
);
}
public record Course(
String code,
String title
) {
}
}
Important operation:
putIfAbsent()
captures:
register only when code doesn't already exist
without manual check-then-act।
Practical Example — Read-Heavy Listener Registry
import java.util.concurrent.CopyOnWriteArrayList;
public final class ListenerRegistry {
private final CopyOnWriteArrayList<String> listeners =
new CopyOnWriteArrayList<>();
public void register(
String listener
) {
listeners.addIfAbsent(
listener
);
}
public void notifyListeners() {
for (
String listener
: listeners
) {
System.out.println(
"Notify "
+ listener
);
}
}
}
This is reasonable when:
registration rare
notification iteration frequent
Practical Example — Bounded Work Queue
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public final class WorkQueue {
private final BlockingQueue<String> tasks =
new ArrayBlockingQueue<>(
100
);
public void submit(
String task
) throws InterruptedException {
tasks.put(
task
);
}
public String next()
throws InterruptedException {
return tasks.take();
}
}
API tells us:
submit()
→ may wait when full
next()
→ may wait when empty
Practice 1 — Virtual or Platform Thread?
There may be thousands of independent tasks that mostly wait on blocking operations।
Which modern Thread model should you consider?
Answer
Virtual Threads
are a strong candidate।
Practice 2 — CPU-Bound Task
Will 100,000 Virtual Threads make a heavy CPU calculation 100,000 times faster?
Answer
No।
CPU remains limited by available hardware।
Practice 3 — Start Virtual Thread
Solution
Thread worker =
Thread.startVirtualThread(
() ->
doWork()
);
Practice 4 — Per-Task Executor
Solution
var executor =
Executors
.newVirtualThreadPerTaskExecutor();
Practice 5 — Pool Virtual Threads?
Should Virtual Threads normally be treated like scarce Platform Threads and reused through a small fixed pool?
Answer
No।
Task-per-Virtual-Thread is the intended model।
Practice 6 — Resource Capacity
Thousands of tasks exist but only 10 may access a resource simultaneously।
What can model the limit?
Answer
A:
Semaphore
can be appropriate।
Practice 7 — Race Condition
Does Virtual Thread make this safe?
count++;
Answer
No।
Practice 8 — Concurrent Map
Need concurrent key-value registry।
Answer
Consider:
ConcurrentHashMap
Practice 9 — Insert If Missing
Answer
putIfAbsent()
Practice 10 — Create If Missing
Answer
computeIfAbsent()
Practice 11 — Read-Heavy List
Reads and iterations are very frequent, mutations very rare।
Answer
Consider:
CopyOnWriteArrayList
Practice 12 — Write-Heavy List
Should CopyOnWriteArrayList automatically be chosen?
Answer
No।
Copy-on-write mutation can be expensive।
Practice 13 — Consumer Waits for Work
Answer
queue.take();
Practice 14 — Producer Waits for Space
On a bounded BlockingQueue:
queue.put(
task
);
Practice 15 — Whole Workflow
Does ConcurrentHashMap make a multi-step operation involving several objects automatically atomic?
Answer
No।
The complete business invariant still needs correct concurrency design।
True or False
- Virtual Thread is still a Java
Thread. - Virtual Threads are mainly useful for high-concurrency waiting-heavy workloads.
- Virtual Threads make CPU calculations intrinsically faster.
Thread.startVirtualThread()starts a Virtual Thread.newVirtualThreadPerTaskExecutor()creates a Virtual Thread for each task.- Virtual Threads should normally be pooled as scarce workers.
Semaphorecan limit concurrent access to a resource.- Virtual Threads eliminate race conditions.
ConcurrentHashMapprovides concurrent Map operations.putIfAbsent()can avoid manual insert check-then-act.CopyOnWriteArrayListis best for every write-heavy workload.BlockingQueue.take()may wait until an element exists.BlockingQueue.put()may wait for capacity.- A bounded queue can provide backpressure.
- Thread-safe collections automatically make every business workflow thread-safe.
Answers
1. True
2. True
3. False
4. True
5. True
6. False
7. True
8. False
9. True
10. True
11. False
12. True
13. True
14. True
15. False
Knowledge Check
Question 1
Platform Thread এবং Virtual Thread-এর main conceptual difference কী?
Question 2
Virtual Threads কোন ধরনের workload-এর জন্য particularly useful?
Question 3
কেন Virtual Threads CPU-intensive code automatically faster করে না?
Question 4
Task-per-thread model কী?
Question 5
কেন Virtual Threads pool করা সাধারণত দরকার হয় না?
Question 6
Resource concurrency কীভাবে Virtual Thread count থেকে separately limit করা যায়?
Question 7
Virtual Threads কি thread-safety rules change করে?
Question 8
ConcurrentHashMap কেন useful?
Question 9
putIfAbsent() কেন manual containsKey() + put() থেকে safer?
Question 10
computeIfAbsent() কী problem solve করে?
Question 11
CopyOnWriteArrayList কোন workload-এর জন্য suited?
Question 12
BlockingQueue normal Queue-এর চেয়ে কী extra capability দেয়?
Question 13
put() এবং take() কী করে?
Question 14
Backpressure কীভাবে bounded queue-এর মাধ্যমে তৈরি হয়?
Question 15
Thread-safe collection এবং thread-safe workflow-এর difference কী?
Knowledge Check Answers
Answer 1
Platform Thread Operating System thread-এর সাথে closely connected এবং তুলনামূলক expensive resource।
Virtual Thread runtime-managed lightweight Thread, যা many concurrent tasks represent করতে পারে এবং waiting-এর সময় একটি specific OS Thread permanently occupy করতে হয় না।
Answer 2
যখন application-এ অনেক concurrent tasks আছে এবং তারা significant সময় blocking/waiting-এ কাটায়।
Answer 3
Virtual Threads CPU cores বাড়ায় না।
CPU-intensive computation hardware processing capacity দ্বারাই limited।
Answer 4
প্রতিটি concurrent task-এর জন্য একটি separate Virtual Thread ব্যবহার করা।
Conceptually:
Task
→ Virtual Thread
Answer 5
Traditional pooling scarce Platform Threads reuse করার জন্য প্রয়োজন।
Virtual Threads lightweight এবং task lifetime represent করার জন্য designed।
Answer 6
Scarce resource explicitly limit করা যায়।
Example:
Semaphore
যখন requirement হলো maximum concurrent access control করা।
Answer 7
No।
Shared state, race condition, atomicity, visibility, synchronization—সব rules relevant থাকে।
Answer 8
Multiple threads-এর concurrent key-value access-এর জন্য concurrency-aware Map semantics provide করে।
Answer 9
Manual:
check absent
then insert
দুইটি separate operation।
putIfAbsent() সেই intent এক concurrent operation-এ express করে।
Answer 10
Key-এর mapping না থাকলে value compute এবং associate করতে সাহায্য করে।
Answer 11
যেখানে:
reads and traversals frequent
writes rare
এবং snapshot-style iteration acceptable।
Answer 12
Queue operations প্রয়োজন হলে wait করতে পারে:
data-এর জন্য
বা
capacity-এর জন্য
Answer 13
put()
bounded queue full হলে space-এর জন্য wait করতে পারে।
take()
queue empty হলে element-এর জন্য wait করতে পারে।
Answer 14
Queue full হলে producer আর unlimited rate-এ values add করতে পারে না।
put() অপেক্ষা করে, ফলে producer-এর উপর pressure ফিরে যায়।
Answer 15
Thread-safe collection তার own operations-এর concurrency guarantees দেয়।
Business workflow multiple collections, objects এবং conditions involve করতে পারে।
সেই full invariant automatically atomic হয় না।
Practical Selection Guide
Many waiting-heavy tasks:
Virtual Thread
Per-task Virtual Thread execution:
Executors.newVirtualThreadPerTaskExecutor()
Limit scarce resource access:
Semaphore
Concurrent key-value state:
ConcurrentHashMap
Atomic insert-if-missing:
putIfAbsent()
Compute value if missing:
computeIfAbsent()
Read-heavy, rarely modified List:
CopyOnWriteArrayList
Producer-consumer handoff:
BlockingQueue
Bounded FIFO queue:
ArrayBlockingQueue
Protect explicit shared invariant:
synchronized
Avoid shared mutation where possible:
Immutability
Thread confinement
Stateless design
Core Mental Model
Modern Java concurrency-তে concerns আলাদা করে ভাবুন।
How do I represent many tasks?
→ Virtual Threads
How do I execute tasks?
→ ExecutorService
How many tasks may access a scarce resource?
→ Semaphore / resource capacity
How do tasks exchange work?
→ BlockingQueue
How do threads share key-value data?
→ ConcurrentHashMap
How do I safely share read-heavy lists?
→ CopyOnWriteArrayList
How do I protect a business invariant?
→ Appropriate synchronization/state design
Virtual Threads-এর সবচেয়ে important mental shift:
Platform Thread model:
Threads are scarce workers.
Virtual Thread model:
A thread can represent a task.
কিন্তু:
Cheap threads
≠
Unlimited system capacity
CPU, memory, connections, queue capacity এবং downstream systems এখনও finite।
Lesson Summary
এই lesson-এ আমরা Virtual Threads এবং Concurrent Collections-এর foundation শিখেছি।
আমরা শিখেছি:
- Platform Threads তুলনামূলক expensive execution resources
- Traditional thread pools Platform Threads reuse করে
- Virtual Threads lightweight Java Threads
- Virtual Threads high-concurrency blocking/waiting workloads-এর জন্য particularly useful
- Virtual Threads single CPU task automatically faster করে না
- Virtual Threads scale/throughput improve করতে পারে, CPU speed নয়
Thread.startVirtualThread()Virtual Thread start করতে পারেThread.ofVirtual()builder API provide করেnewVirtualThreadPerTaskExecutor()প্রতিটি task-এর জন্য Virtual Thread create করে- Virtual Threads task-per-thread model support করে
- Virtual Threads traditional fixed worker pool হিসেবে treat করা উচিত নয়
- Cheap Virtual Threads unlimited resources imply করে না
Semaphorescarce resource concurrency independently limit করতে পারে- Virtual Threads race condition বা shared-state problem remove করে না
ConcurrentHashMapconcurrent key-value access-এর জন্য designedputIfAbsent()insert-if-missing operation atomicভাবে express করতে পারেcomputeIfAbsent()missing values create করতে useful- Concurrent Map-এর mutable values-এর জন্য আলাদা thread-safety reasoning দরকার
CopyOnWriteArrayListread-heavy, write-light workloads-এর জন্য useful- Copy-on-write writes relatively expensive
BlockingQueueproducer-consumer coordination support করেput()capacity-এর জন্য wait করতে পারেtake()data-এর জন্য wait করতে পারেArrayBlockingQueuebounded FIFO work queue- Bounded queues backpressure তৈরি করতে পারে
- Concurrent collection-এর individual operation safe হলেও entire business workflow automatically atomic নয়
- Concurrency correctness business invariant-এর level-এ reason করতে হয়
সবচেয়ে important principle:
Virtual Threads concurrency cheap করে।
Concurrent Collections shared data access সহজ করে।
কিন্তু correctness এখনো
state ownership,
resource limits,
and business invariants-এর
উপর নির্ভর করে।
Next Lesson
পরবর্তী lesson:
Module Practice and Assessment
এই final Module 7 assessment-এ আমরা পুরো Modern Java module review করব:
- Lambda Expressions
- Functional Interfaces
- Method References
- Stream API
- Filtering and Transformation
flatMap()- Collectors
- Grouping and Reduction
- Optional
- Modern Date and Time API
var- Switch Expressions
- Pattern Matching
- Records
- Concurrency
- Thread Safety
- Executors
- Future
- CompletableFuture
- Virtual Threads
- Concurrent Collections
Final assessment-এ theory-এর পাশাপাশি integrated practical Java exercises থাকবে।