Executor Framework & CompletableFuture
Thread pool, future ও async composition
1. Stop Creating Threads Manually
new Thread(...).start() is fine for one-off experiments. For real workloads — handling 1000 HTTP
requests, processing 50 files — you need a thread pool. The ExecutorService
interface gives you one: submit Runnables or Callables and the pool schedules them
onto a fixed set of worker threads.
new Thread(...).start() এক-দুটি ক্ষেত্রে ঠিক আছে, কিন্তু ১০০০ HTTP request বা ৫০ ফাইল process করতে thread pool দরকার। ExecutorService interface সেই pool দেয় — Runnable/Callable submit করুন, pool তার worker thread-এ schedule করবে।
2. ExecutorService — Submit and Forget
Create a pool via Executors.newFixedThreadPool(n) (or newCachedThreadPool,
newSingleThreadExecutor). Submit tasks, then shutdown when done.
Executors.newFixedThreadPool(n) দিয়ে pool বানান। task submit করুন, শেষে shutdown — পুরনো task শেষ হয়ে pool বন্ধ হবে।
import java.util.concurrent.*;
class Main {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 5; i++) {
final int id = i;
pool.submit(() -> {
System.out.println("task " + id + " on "
+ Thread.currentThread().getName());
});
}
pool.shutdown();
pool.awaitTermination(5, TimeUnit.SECONDS);
System.out.println("all done");
}
}
3. Callable and Future — Tasks That Return Values
A Runnable has no return value. A Callable<V> returns V (and may
throw checked exceptions). Submitting a Callable gives you a Future<V> —
call .get() to block until the result is ready.
Runnable-এর return নেই। Callable<V> V return করে এবং checked exception throw করতে পারে। submit করলে Future<V> পাবেন — .get() করলে result আসা পর্যন্ত block করবে।
import java.util.concurrent.*;
class Main {
public static void main(String[] args) throws Exception {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f1 = pool.submit(() -> { Thread.sleep(30); return 10; });
Future<Integer> f2 = pool.submit(() -> { Thread.sleep(30); return 32; });
int sum = f1.get() + f2.get(); // blocks until both ready
System.out.println("sum = " + sum);
pool.shutdown();
}
}
4. CompletableFuture — Non-Blocking Async
Future.get() blocks. CompletableFuture lets you chain callbacks that run when the
result arrives — no blocking. This is Java's answer to JavaScript's Promises.
Future.get() block করে। CompletableFuture callback chain করতে দেয় — result এলে তবেই চলবে, block নেই। এটাই Java-র Promise equivalent।
import java.util.concurrent.*;
class Main {
public static void main(String[] args) throws Exception {
CompletableFuture<String> cf =
CompletableFuture.supplyAsync(() -> "Raihan")
.thenApply(String::toUpperCase)
.thenApply(name -> "Hello, " + name);
cf.thenAccept(System.out::println);
cf.get(); // wait for the chain to finish before sandbox exits
}
}
5. Combining Futures — allOf
When you have several independent async calls (e.g., fetch user, fetch orders, fetch prefs), run them in
parallel and wait with allOf.
allOf দিয়ে সবগুলো শেষ হওয়ার পর একবারে handle করুন।
import java.util.concurrent.*;
class Main {
public static void main(String[] args) throws Exception {
CompletableFuture<String> user =
CompletableFuture.supplyAsync(() -> "Raihan");
CompletableFuture<Integer> orders =
CompletableFuture.supplyAsync(() -> 7);
CompletableFuture<String> prefs =
CompletableFuture.supplyAsync(() -> "dark-mode");
CompletableFuture.allOf(user, orders, prefs).join();
System.out.printf("user=%s orders=%d prefs=%s%n",
user.get(), orders.get(), prefs.get());
}
}
6. Virtual Threads (Java 21) — A Preview
Java 21 introduced virtual threads (Project Loom). They look like regular threads but are managed by the JVM, not the OS. Creating a million of them is fine. For blocking I/O (HTTP, DB) they are revolutionary.
import java.util.concurrent.*;
class Main {
public static void main(String[] args) throws Exception {
try (ExecutorService vpool = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 5; i++) {
final int id = i;
vpool.submit(() -> {
System.out.println("virtual task " + id);
});
}
} // close() waits for all tasks
System.out.println("all done");
}
}
7. Vocabulary
| Term | Meaning | বাংলায় |
|---|---|---|
| ExecutorService | Thread-pool interface that schedules submitted tasks. | task schedule করার thread-pool interface। |
| Runnable / Callable | Task types — no result / returns a value. | task — return-less / value-return। |
| Future | Handle to a future result — .get() blocks. | ভবিষ্যৎ result-এর handle — .get() block করে। |
| CompletableFuture | Composable, non-blocking future with callbacks. | chainable, non-blocking future। |
| thenApply / thenCompose | Transform result / chain another async step. | result transform / পরবর্তী async step। |
| Virtual thread | Lightweight JVM-managed thread (Java 21+). | JVM-managed lightweight thread। |
8. Practice Problems
-
Submit three tasks to a fixed pool of size 2 and print which thread ran each.size-2 fixed pool-এ তিনটি task submit করুন, প্রতিটির thread-নাম print করুন।
Show Answer (উত্তর দেখুন)
Main.javaimport java.util.concurrent.*; class Main { public static void main(String[] args) throws Exception { ExecutorService p = Executors.newFixedThreadPool(2); for (int i = 1; i <= 3; i++) { final int id = i; p.submit(() -> System.out.println("task "+id+" on "+ Thread.currentThread().getName())); } p.shutdown(); p.awaitTermination(5, TimeUnit.SECONDS); } } -
Use two
Callables that each return an int and sum the results.দুটিCallableথেকে int পেয়ে যোগ করুন।Show Answer (উত্তর দেখুন)
Main.javaimport java.util.concurrent.*; class Main { public static void main(String[] args) throws Exception { ExecutorService p = Executors.newFixedThreadPool(2); Future<Integer> a = p.submit(() -> 100); Future<Integer> b = p.submit(() -> 42); System.out.println("sum = " + (a.get() + b.get())); p.shutdown(); } } -
Chain three
thenApplysteps: start with 10, double, add 1, square it.তিনটিthenApplychain — 10 থেকে শুরু, দ্বিগুণ, +১, বর্গ।Show Answer (উত্তর দেখুন)
Main.javaimport java.util.concurrent.*; class Main { public static void main(String[] args) throws Exception { int r = CompletableFuture.supplyAsync(() -> 10) .thenApply(x -> x * 2) .thenApply(x -> x + 1) .thenApply(x -> x * x) .get(); System.out.println(r); } } -
Explain in 2 sentences the difference between
thenApplyandthenCompose.দুই বাক্যে —thenApplyবনামthenCompose।Show Answer (উত্তর দেখুন)
Answer:
thenApplytakes a sync functionT -> Uand wraps the result in a new future, so you cannot chain another async call inside it without nesting.thenComposetakesT -> CompletableFuture<U>and flattens — perfect when the next step is itself async, like fetching related data.thenApplysync function (T→U) নেয় — আবার async call থাকলে nested future তৈরি হবে।thenComposeT→CompletableFuture<U>নিয়ে flatten করে — পরবর্তী step নিজেই async হলে পারফেক্ট। -
Run three async suppliers in parallel and combine their results with
allOf.allOfদিয়ে তিনটি async supplier parallel-এ চালিয়ে ফলাফল combine করুন।Show Answer (উত্তর দেখুন)
Main.javaimport java.util.concurrent.*; class Main { public static void main(String[] args) throws Exception { CompletableFuture<Integer> a = CompletableFuture.supplyAsync(() -> 1); CompletableFuture<Integer> b = CompletableFuture.supplyAsync(() -> 2); CompletableFuture<Integer> c = CompletableFuture.supplyAsync(() -> 3); CompletableFuture.allOf(a, b, c).join(); System.out.println(a.get() + b.get() + c.get()); } }
Summary — Module 39
Stop managing threads by hand. Use ExecutorService for pools, Callable/Future
for tasks that return values, and CompletableFuture for non-blocking composition with
thenApply, thenCompose, and allOf. Java 21 virtual threads make
"one thread per task" cheap again — the future of Java concurrency.
ExecutorService, value-return task-এ Callable/Future, non-blocking composition-এ CompletableFuture। Java 21 virtual thread — "one thread per task" আবার সস্তা।