থ্রেড আসলে কী? OS থ্রেড বনাম JVM থ্রেড

java.lang.Thread-এ হাত দেওয়ার আগে সঠিক মানসিক চিত্র তৈরি করুন: অপারেটিং-সিস্টেম পর্যায়ে একটি থ্রেড আসলে কী, এবং JVM কীভাবে তার ওপর বসে।

বেশিরভাগ জাভা ডেভেলপার Thread-কে একটি ধারণা হিসেবে বোঝার অনেক আগেই একটি API হিসেবে চিনে ফেলে। এই ক্রমটি পরে সমস্যা তৈরি করে, কারণ API-টি সেই যন্ত্রাংশ লুকিয়ে রাখে যা ব্যাখ্যা করে থ্রেড কেন এমন আচরণ করে। কোনো কনকারেন্ট কোড লেখার আগে, একটি থ্রেড আসলে কী, তার একটি পরিষ্কার মানসিক মডেল তৈরি করে নেওয়া সাহায্য করে।

থ্রেড হলো নির্বাহের একটি পথ, কোনো প্রোগ্রাম নয়

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

অপারেটিং সিস্টেম java.lang.Thread সম্পর্কে কিছুই জানে না

প্রতিটি মূলধারার JVM-এ, একটি জাভা থ্রেড এক-এর-বদলে-একভাবে একটি বাস্তব OS থ্রেড দ্বারা সমর্থিত। আপনি যখন thread.start() কল করেন, JVM অপারেটিং সিস্টেমকে একটি কার্নেল থ্রেড তৈরি করতে বলে, এবং OS সিডিউলার — JVM নয় — সিদ্ধান্ত নেয় কোনো মুহূর্তে কোন থ্রেড কোন CPU কোরে চলবে।
java.lang.Thread মূলত একটি হাতল: একটি জাভা অবজেক্ট যা আপনাকে একটি স্থিতিশীল API-এর মাধ্যমে সেই আন্ডারলায়িং OS থ্রেডের সাথে কথা বলতে দেয়। এটি অন্তর্দৃষ্টিপূর্ণ কারণ এটি এমন বেশ কিছু বিষয় ব্যাখ্যা করে যা অভিজ্ঞ ডেভেলপাররা শেষপর্যন্ত দেখে:

  • থ্রেড তৈরি কেন তুলনামূলকভাবে ব্যয়বহুল
  • JVM কেন পুরোপুরি সিডিউলিং ন্যায্যতা নিশ্চিত করতে পারে না
  • থ্রেড ডাম্প কেন জাভা থ্রেড নামের পাশাপাশি OS-স্তরের থ্রেড আইডেন্টিফায়ার দেখায়

আপনার প্রোগ্রামে ইতিমধ্যেই একাধিক থ্রেড আছে

এমনকি সরলতম public static void main একটি মাল্টি-থ্রেডেড প্রসেসের ভেতর চলে। আপনি JVM-কে প্রতিটি সক্রিয় থ্রেড জিজ্ঞেস করে এটি দেখতে পারেন:

import java.util.Comparator;

public class ThreadCensus {
    public static void main(String[] args) {
        Thread.getAllStackTraces().keySet().stream()
                .sorted(Comparator.comparing(Thread::getName))
                .forEach(t -> System.out.printf(
                        "%-25s daemon=%-5s priority=%d%n",
                        t.getName(),
                        t.isDaemon(),
                        t.getPriority()));
    }
}

এই কোডটি JVM-এর ভেতর বর্তমানে চলমান সমস্ত থ্রেড তালিকাবদ্ধ করে, তাদের নাম অনুযায়ী সাজায় এবং প্রতিটি থ্রেডের নাম, ডেমন অবস্থা ও প্রায়োরিটি ছাপায়। এটি আপনাকে কখনো সৃষ্টি করেননি এমন থ্রেড দেখাবে: main, Reference Handler, Finalizer, Signal Dispatcher, এক বা একাধিক GC থ্রেড এবং একটি JIT কম্পাইলার থ্রেড। main একমাত্র আপনার কোড চালায়; বাকিগুলো JVM রানটাইমের নিজস্ব এবং বেশিরভাগ সময় ডেমন থ্রেড হিসেবে নীরবে ব্যাকগ্রাউন্ডে চলে।

যে মডেল সামনে নিয়ে চলবেন

এই চিত্রটি মনে রাখুন: একটি ভাগাভাগি করা হিপ, অসংখ্য স্বতন্ত্র কল স্ট্যাক, এবং Thread API-এর আড়ালে থেকে একটি OS সিডিউলার পুরো খেলা চালাচ্ছে। এই সিরিজের পরবর্তী প্রতিটি বিষয় — থ্রেড পুল, সিঙ্ক্রোনাইজেশন, মেমোরি ভিজিবিলিটি, ভার্চুয়াল থ্রেড — আসলে এই মডেল থেকে উদ্ভূত দুটি খরচ পরিচালনার কৌশল: থ্রেড তৈরির খরচ এবং ভাগাভাগি করা মেমোরি সমন্বয়ের খরচ।

Share