থ্রেড তৈরির তিনটি চেনা পথ, একটি নতুন

Phase 1-এ তুমি শিখেছিলে থ্রেড তৈরির তিনটি সাধারণ উপায়: Thread ক্লাস এক্সটেন্ড করা, Runnable implement করা, অথবা ExecutorService-এ টাস্ক সাবমিট করা। ভার্চুয়াল থ্রেড এই সবগুলোর সাথে সামঞ্জস্যপূর্ণ — কারণ এটা এখনও একটা java.lang.Thread-ই, শুধু ভিন্নভাবে তৈরি ও শিডিউল করা হয়।

Thread.ofVirtual() দিয়ে সরাসরি তৈরি

Thread vt = Thread.ofVirtual()
        .name("worker-", 0)
        .unstarted(() -> {
            System.out.println("Running on: " + Thread.currentThread());
        });
vt.start();
vt.join();

অথবা আরও ছোট করে, সরাসরি চালু করার জন্য:

Thread vt = Thread.startVirtualThread(() -> {
    System.out.println("এই থ্রেডটা ভার্চুয়াল: " + Thread.currentThread().isVirtual());
});
vt.join();

Thread.currentThread().isVirtual() কল করে তুমি রানটাইমে যাচাই করতে পারো থ্রেডটা ভার্চুয়াল কিনা — ডিবাগিং আর লগিং-এর সময় এটা কাজে লাগে।

Executor দিয়ে ব্যবহারিক প্যাটার্ন

সরাসরি Thread.startVirtualThread() কল করা সাধারণত সেরা প্র্যাকটিস না — Phase 3-এ তুমি যেমন ExecutorService ব্যবহার করে টাস্ক সাবমিশন আর থ্রেড ম্যানেজমেন্ট আলাদা করেছিলে, ভার্চুয়াল থ্রেডের জন্যও একই কাজ করে Executors.newVirtualThreadPerTaskExecutor():

try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    List<Future<String>> futures = new ArrayList<>();

    for (String userId : userIds) {
        futures.add(executor.submit(() -> fetchUserProfile(userId)));
    }

    for (Future<String> f : futures) {
        System.out.println(f.get());
    }
} // executor.close() স্বয়ংক্রিয়ভাবে সব টাস্ক শেষ হওয়া পর্যন্ত অপেক্ষা করে

লক্ষ্য করো এই এক্সিকিউটরে কোনো fixed pool size নেই — কারণ ভার্চুয়াল থ্রেড pooled হয় না (পরের একটা আর্টিকেলে দেখব কেন এটা গুরুত্বপূর্ণ)। প্রতিটি submit() কল একটা নতুন ভার্চুয়াল থ্রেড তৈরি করে, যেটা কাজ শেষ হলে সহজভাবে ছুঁড়ে ফেলে দেওয়া হয় (garbage collected) — ঠিক যেমন একটা সাধারণ অবজেক্ট।

প্ল্যাটফর্ম থ্রেড তৈরির সাথে তুলনা

বিষয় Platform Thread Virtual Thread
তৈরির খরচ মাইক্রোসেকেন্ড, স্ট্যাক প্রি-অ্যালোকেটেড ন্যানোসেকেন্ড-স্কেল, স্ট্যাক ছোট ও বাড়ে প্রয়োজনমতো
ডিফল্ট নাম Thread-0, Thread-1... নাম না দিলে ফাঁকা থাকে
ডিফল্ট priority/daemon সাধারণ priority, non-daemon সবসময় daemon (JVM এদের জন্য অপেক্ষা করে না)
Thread pool-এ রাখা হ্যাঁ, সাধারণ অভ্যাস না, প্রতি টাস্কে নতুন তৈরি হয়

একটা গুরুত্বপূর্ণ পার্থক্য: ভার্চুয়াল থ্রেড সবসময় daemon থ্রেড — মানে main() মেথড শেষ হয়ে গেলে JVM তাদের জন্য অপেক্ষা করবে না, যেমনটা তুমি Phase 1-এর "Daemon vs User Threads" আর্টিকেলে দেখেছ। তাই join() কল করে বা executor-কে properly close করে নিশ্চিত হতে হয় যে কাজ শেষ হয়েছে।

দ্রুত একটা তুলনামূলক পরীক্ষা

Instant start = Instant.now();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 100_000).forEach(i ->
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));
            return i;
        })
    );
}
System.out.println("সময় লাগলো: " + Duration.between(start, Instant.now()));

এই কোড এক লাখ টাস্ক চালায়, প্রতিটি ১ সেকেন্ড ঘুমায় (একটা ব্লকিং I/O-এর সিমুলেশন)। প্ল্যাটফর্ম থ্রেড পুল দিয়ে করলে এক লাখ থ্রেড তৈরি করতেই মেমরি ফুরিয়ে যেতে পারে, কিন্তু ভার্চুয়াল থ্রেডে এটা কয়েক সেকেন্ডের মধ্যেই শেষ হয়ে যায় — কারণ সব থ্রেড একসাথে "ঘুমিয়ে" আছে, কোনো ক্যারিয়ার থ্রেড ব্লক করে রাখছে না।

Share