নিয়ম #১: ভার্চুয়াল থ্রেড পুল করো না

Phase 3-এ তুমি শিখেছিলে থ্রেড পুল কেন গুরুত্বপূর্ণ — থ্রেড তৈরির খরচ বেশি বলে, পুল করে রিইউজ করা হয়। ভার্চুয়াল থ্রেডে এই যুক্তিটাই উল্টে যায়। ভার্চুয়াল থ্রেড তৈরির খরচ এতই কম যে পুল করাটা কোনো লাভ দেয় না, বরং ক্ষতি করে — কারণ একটা ফিক্সড-সাইজ পুল মানেই একটা সীমা, আর সেই সীমাই তো আমরা ভার্চুয়াল থ্রেড দিয়ে দূর করতে চেয়েছিলাম।

// ভুল: ভার্চুয়াল থ্রেড পুল করা
ExecutorService bad = Executors.newFixedThreadPool(200, Thread.ofVirtual().factory());

// ঠিক: প্রতি টাস্কে নতুন ভার্চুয়াল থ্রেড, কোনো সীমা ছাড়া
ExecutorService good = Executors.newVirtualThreadPerTaskExecutor();

যদি কনকারেন্সি সীমিত করার দরকার হয় (যেমন একটা ডাউনস্ট্রিম সার্ভিসকে overload করা এড়াতে), সেই কাজটা Semaphore দিয়ে করো — থ্রেড পুল সাইজ দিয়ে না।

private final Semaphore limiter = new Semaphore(50); // সর্বোচ্চ ৫০টা কনকারেন্ট কল

void callDownstream() throws InterruptedException {
    limiter.acquire();
    try {
        restClient.get(url);
    } finally {
        limiter.release();
    }
}

নিয়ম #২: CPU-বাউন্ড কাজে ভার্চুয়াল থ্রেড ব্যবহার কোরো না

Phase 5-এর শুরুতে যেমন বলা হয়েছিল — ভার্চুয়াল থ্রেড I/O-বাউন্ড কাজের জন্য, CPU-বাউন্ড কাজের জন্য না। যদি তোমার কাজ ভারী গণনা করে (ইমেজ প্রসেসিং, এনক্রিপশন, বড় ডাটাসেট সর্ট করা), হাজার হাজার ভার্চুয়াল থ্রেড তৈরি করলে কোনো লাভ নেই — কারণ CPU কোরের সংখ্যা তো বাড়ছে না, বরং হাজার হাজার থ্রেডের মধ্যে শিডিউলিং ওভারহেড যোগ হবে। এই ধরনের কাজের জন্য আগের মতোই একটা bounded ExecutorService (Phase 3-এর newFixedThreadPool) ব্যবহার করো, CPU কোরের সংখ্যার কাছাকাছি সাইজ দিয়ে।

নিয়ম #৩: হট পাথে synchronized এড়াও

আগের আর্টিকেলে দেখা পিনিং সমস্যাটা মনে রাখো — যেকোনো synchronized ব্লক যেটা ব্লকিং I/O করে, সেটাকে ReentrantLock-এ কনভার্ট করো, নাহলে ক্যারিয়ার থ্রেড দখল হয়ে যাবে।

নিয়ম #৪: মনিটরিং সেটআপ করো

প্রোডাকশনে যাওয়ার আগে দুটো জিনিস অন করো:

-Djdk.tracePinnedThreads=full

আর JFR-এ jdk.VirtualThreadPinned, jdk.VirtualThreadSubmitFailed ইভেন্টগুলো মনিটর করো — এগুলো দেখাবে কখন ভার্চুয়াল থ্রেড সিস্টেম প্রত্যাশিত মতো আচরণ করছে না (যেমন OS-লেভেল থ্রেড লিমিট, যা ভার্চুয়াল থ্রেডের ক্ষেত্রেও প্রযোজ্য, কারণ শেষ পর্যন্ত এগুলো ক্যারিয়ার থ্রেডে চলে, আর ক্যারিয়ার থ্রেড আসলে OS থ্রেড)।

নিয়ম #৫: থার্ড-পার্টি লাইব্রেরি যাচাই করো

তোমার JDBC ড্রাইভার, HTTP ক্লায়েন্ট, বা মেসেজিং লাইব্রেরি ভার্চুয়াল থ্রেড-অ্যাওয়্যার কিনা যাচাই করো। পুরনো লাইব্রেরি যেগুলো ইন্টারনালি synchronized বা fixed-size connection pool ব্যবহার করে, সেগুলো ভার্চুয়াল থ্রেডের সুবিধা কেড়ে নিতে পারে। আধুনিক JDBC ড্রাইভার (PostgreSQL, MySQL Connector/J-এর সাম্প্রতিক ভার্সন) আর Java 11+ HttpClient ইতিমধ্যে virtual-thread-friendly।

কখন ভার্চুয়াল থ্রেড ব্যবহার করবে না

  • CPU-বাউন্ড, গণনাভিত্তিক কাজ (উপরে যেমন বলা হয়েছে)।
  • এমন কোড যেখানে থ্রেড আইডেন্টিটির উপর নির্ভরশীল লজিক আছে (যেমন ThreadLocal ভিত্তিক কানেকশন-প্রতি-থ্রেড প্যাটার্ন) — এগুলো আগে ScopedValue বা অন্য ডিজাইনে রিফ্যাক্টর করা দরকার।
  • খুব অল্প-সংখ্যক দীর্ঘস্থায়ী ব্যাকগ্রাউন্ড থ্রেড (যেমন একটা সিঙ্গেল হার্টবিট থ্রেড) — এখানে সাধারণ প্ল্যাটফর্ম থ্রেডই যথেষ্ট, ভার্চুয়াল থ্রেডের কোনো বাড়তি সুবিধা নেই।

সারসংক্ষেপ: পুরো Phase 5 এক নজরে

এই ফেজে তুমি দেখলে কেন থ্রেড-পার-রিকোয়েস্ট মডেল স্কেলে ভেঙে পড়ে, কিভাবে ভার্চুয়াল থ্রেড M:N শিডিউলিং দিয়ে সেটা সমাধান করে, pinning কোথায় এই মডেল ভেঙে যায়, স্ট্রাকচার্ড কনকারেন্সি কিভাবে গ্রুপ টাস্ককে একটা ইউনিট হিসেবে ম্যানেজ করে, আর ScopedValue কিভাবে কনটেক্সট প্রচার করে নিরাপদে। এই সবগুলো একসাথে জাভাকে Node.js বা Go-এর মতো ভাষার কাছাকাছি নিয়ে আসে — উচ্চ-concurrency I/O-বাউন্ড কাজের জন্য, অথচ কোডটা এখনও চেনা, ব্লকিং-স্টাইলেই থেকে যায়।

Share