Executor Framework & CompletableFuture

Thread pool, future ও async composition

Read: ~32 min Advanced 5 practice problems Live code runner

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 করবে।
Why pools? Creating a thread costs real OS resources. Reusing them is 10–100× faster. A pool also bounds the concurrency — no more 5000 threads melting your laptop.

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 বন্ধ হবে।
Main.java
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 করবে।
Main.java
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।
CompletableFuture — chain async steps without blocking supplyAsync fetchUser(id) thenApply u → u.name() thenCompose n → fetchOrders(n) thenAccept print each step runs when the previous completes — main thread never blocks Figure 39.1 — supplyAsync → thenApply → thenCompose → thenAccept।
Main.java
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.

একাধিক স্বাধীন async call একসাথে চালিয়ে allOf দিয়ে সবগুলো শেষ হওয়ার পর একবারে handle করুন।
Main.java
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.

Java 21-এ virtual thread (Project Loom) এসেছে — দেখতে সাধারণ thread, কিন্তু JVM-manage করে (OS নয়)। লক্ষ লক্ষ বানানো যায়। blocking I/O-এর জন্য গেম-চেঞ্জার।
Main.java (Java 21+)
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

TermMeaningবাংলায়
ExecutorServiceThread-pool interface that schedules submitted tasks.task schedule করার thread-pool interface।
Runnable / CallableTask types — no result / returns a value.task — return-less / value-return।
FutureHandle to a future result — .get() blocks.ভবিষ্যৎ result-এর handle — .get() block করে।
CompletableFutureComposable, non-blocking future with callbacks.chainable, non-blocking future।
thenApply / thenComposeTransform result / chain another async step.result transform / পরবর্তী async step।
Virtual threadLightweight JVM-managed thread (Java 21+).JVM-managed lightweight thread।

8. Practice Problems

  1. 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.java
    import 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);
        }
    }
  2. Use two Callables that each return an int and sum the results.
    দুটি Callable থেকে int পেয়ে যোগ করুন।
    Show Answer (উত্তর দেখুন)
    Main.java
    import 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();
        }
    }
  3. Chain three thenApply steps: start with 10, double, add 1, square it.
    তিনটি thenApply chain — 10 থেকে শুরু, দ্বিগুণ, +১, বর্গ।
    Show Answer (উত্তর দেখুন)
    Main.java
    import 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);
        }
    }
  4. Explain in 2 sentences the difference between thenApply and thenCompose.
    দুই বাক্যে — thenApply বনাম thenCompose।
    Show Answer (উত্তর দেখুন)

    Answer: thenApply takes a sync function T -> U and wraps the result in a new future, so you cannot chain another async call inside it without nesting. thenCompose takes T -> CompletableFuture<U> and flattens — perfect when the next step is itself async, like fetching related data.

    thenApply sync function (T→U) নেয় — আবার async call থাকলে nested future তৈরি হবে। thenCompose T→CompletableFuture<U> নিয়ে flatten করে — পরবর্তী step নিজেই async হলে পারফেক্ট।

  5. Run three async suppliers in parallel and combine their results with allOf.
    allOf দিয়ে তিনটি async supplier parallel-এ চালিয়ে ফলাফল combine করুন।
    Show Answer (উত্তর দেখুন)
    Main.java
    import 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.

manually thread manage করা বন্ধ করুন। pool-এ ExecutorService, value-return task-এ Callable/Future, non-blocking composition-এ CompletableFuture। Java 21 virtual thread — "one thread per task" আবার সস্তা।

Next Module → Concurrent Collections।