মূল বিষয়বস্তুতে যান

CooLWJ-এ খুঁজুন

选择搜索范围,输入关键词后按回车。

ভাষা

সামগ্রিক দৃষ্টিতে প্রযুক্তি-Java মাল্টিথ্রেডিংয়ের বিবর্তনের ইতিহাস

ভূমিকা

2022 সালের 09 মাসের 22 তারিখে JDK19 প্রকাশিত হয়। এই সংস্করণের সবচেয়ে উল্লেখযোগ্য বৈশিষ্ট্য হলো ভার্চুয়াল থ্রেড সমর্থন; এর ফলে হালকা থ্রেডের পরিবারে আরও এক শক্তিশালী সদস্য যোগ হলো। ভার্চুয়াল থ্রেড JVM-কে অপারেটিং সিস্টেমের মাধ্যমে থ্রেড শিডিউল করানোর বাধ্যবাধকতা থেকে মুক্ত করে; JVM নিজেই থ্রেড শিডিউল করে। আসলে শুরুর দিকে sun Solaris অপারেটিং সিস্টেমের ভার্চুয়াল মেশিনে JVM দিয়ে থ্রেড শিডিউল করার ব্যবস্থা বাস্তবায়ন করেছিল। তবে জটিলতা ও রক্ষণাবেক্ষণের কথা বিবেচনা করে শেষ পর্যন্ত আবার অপারেটিং সিস্টেম দিয়ে থ্রেড শিডিউল করার পদ্ধতিতেই ফিরে যাওয়া হয়।

“长安归来锦衣客,昨日城南起新宅。”(চাংআন থেকে ফিরে এলেন রেশমি পোশাকের অতিথি, গতকাল নগরের দক্ষিণে উঠল তাঁর নতুন বাড়ি।)এই পথচলার দিকে ফিরে তাকালে, মাল্টিথ্রেডিংয়ের ধারণাগুলি চোখ ধাঁধিয়ে দেয়, অনলাইনেও এর ব্যাখ্যার শেষ নেই, তবু মনে হয় সামগ্রিক দৃষ্টিভঙ্গির অভাব আছে। তাই লেখক Java-র মাল্টিথ্রেডিংয়ের বিবর্তনের ইতিহাস পদ্ধতিগতভাবে সাজিয়েছেন, আশা করি মাল্টিথ্রেডিংয়ের জ্ঞান আয়ত্ত করতে এটি আপনার কাজে লাগবে।

এই লেখায় যা আলোচনা করা হবে না:

1 নির্দিষ্ট প্রযুক্তিগত বিষয়ের বিস্তারিত বাস্তবায়ননীতি আলোচনা করা হবে না,সোর্স কোড বিশ্লেষণ করা হবে না,চিত্রও আঁকা হবে না,এই লেখায় আগ্রহের কোনো ধারণা বা প্রযুক্তি পেলে নিজে অনুসন্ধান করতে পারেন 2 সমসাময়িক কাজ সমর্থনকারী লাইব্রেরি ও ফ্রেমওয়ার্ক,যেমন Quasar、Akka、Guava ইত্যাদি আলোচনা করা হবে না

এই লেখায় কী আলোচনা করা হয়েছে

1 JDK-এর মাল্টিথ্রেডিংয়ের বিবর্তনের ইতিহাস আলোচনা 2 বিবর্তনের কিছু প্রযুক্তির কার্যপদ্ধতি ও পটভূমি এবং সেগুলি কোন সমস্যার সমাধান করেছে তা আলোচনা 3 কিছু প্রযুক্তি সম্পর্কে লেখকের মতামত আলোচনা, ভিন্ন মত থাকলে মন্তব্যের ঘরে আলোচনায় স্বাগত

মাইলফলক

আগের নিয়মেই প্রথমে একটি পরিসংখ্যানের সারণি দেওয়া যাক। এতে বিভিন্ন প্রজন্মের JDK-তে থ্রেডসংক্রান্ত মূল ধারণাগুলো সাজানো হয়েছে। এখানে হয়তো পুরোপুরি যথাযথ নয় এমন একটি তুলনা করা যায়: মাল্টিথ্রেডিংয়ের বিবর্তনকে গাড়ির সঙ্গে মিলিয়ে দেখলে, এর ধাপগুলো হলো ম্যানুয়াল গিয়ারের যুগ (JDK1.4 ও তার আগের সংস্করণ), স্বয়ংক্রিয় গিয়ারের যুগ(JDK5-JDK18), এবং স্বচালিত গাড়ির যুগ (JDK19 ও পরবর্তী সংস্করণ)। এই তুলনার উদ্দেশ্য কেবল পাঠককে বোঝানো যে JDK5-এর পর থেকে মাল্টিথ্রেডিং আরও স্বচ্ছন্দে নিয়ন্ত্রণ করা যায়। JDK19-এর পর বিষয়টি শুধু স্বাচ্ছন্দ্যে সীমাবদ্ধ থাকেনি; IO-নির্ভর সেবার কর্মক্ষমতায় এটি গুণগত উল্লম্ফন এনেছে।

যুগসংস্করণপ্রকাশের সময়মূল ধারণা
ম্যানুয়াল গিয়ারJDK1.01996-01-23Thread এবং Runnable
ম্যানুয়াল গিয়ারJDK1.21998-12-04ThreadLocal、Collections
স্বয়ংক্রিয় গিয়ারJDK1.5/5.02004-09-30Java মেমরি মডেল সুস্পষ্ট করা、কনকারেন্সি প্যাকেজ প্রবর্তন
স্বয়ংক্রিয় গিয়ারJDK1.6/6.02006-12-11synchronized-এর অপ্টিমাইজেশন
স্বয়ংক্রিয় গিয়ারJDK1.7/7.02011-07-28Fork/Join ফ্রেমওয়ার্ক
স্বয়ংক্রিয় গিয়ারJDK1.8/8.02014-03-18CompletableFuture、Stream
স্বয়ংক্রিয় গিয়ারJDK1.9/9.02014-09-08লক নিয়ে প্রতিযোগিতা সামলানোর ব্যবস্থা উন্নত করা
স্বয়ংক্রিয় গিয়ারJDK102018-03-21থ্রেড স্থানীয় নিয়ন্ত্রণ
স্বয়ংক্রিয় গিয়ারJDK152020-09-15বায়াসড লক নিষ্ক্রিয় করা ও অবলুপ্ত ঘোষণা করা
স্বয়ংচালিত গাড়ি চালনাJDK192022-09-22ভার্চুয়াল থ্রেড

2f98c71c0436418d8d260d75cb921c81~tplv-73owjymdk6-jj-mark-v1_0_0_0_0_5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5Lqs5Lic5LqR5byA5Y-R6ICF_q75.webp

ম্যানুয়াল গিয়ারের যুগ

JDK1.4 এবং তার আগের সংস্করণগুলোকে আমি মাল্টিথ্রেডিংয়ের “ম্যানুয়াল গিয়ার” যুগ বলি, একে নেটিভ মাল্টিথ্রেডিং যুগও বলা যায়। থ্রেড পরিচালনা তখনও তুলনামূলকভাবে আদিম ছিল, ব্যবহারের জন্য কোনো থ্রেড পুল ছিল না। বারবার থ্রেড তৈরির ফলে সম্পদের অপচয় এড়াতে ডেভেলপারদের নিজের হাতে টুল লিখতে হতো এবং শেয়ার করা সম্পদে নিজে লক বসাতে হতো। এই যুগেই বহু উৎকৃষ্ট মাল্টিথ্রেডিং ফ্রেমওয়ার্কের জন্ম হয়, যার মধ্যে সবচেয়ে বিখ্যাতটি JDK5.0-এ গৃহীত হয়।

JDK 1.0 Thread ও Runnable

1996 সালের 1 মাসের JDK1.0 সংস্করণেই শুরু থেকে Java-এর সবচেয়ে মৌলিক থ্রেড মডেল প্রতিষ্ঠিত হয়েছিল,এবং,পরবর্তী মেরামত ও সংশোধনেও এই থ্রেড মডেলে মৌলিক কোনো পরিবর্তন হয়নি,একে ধারাবাহিকতা বজায় রাখা একটি ভালো নকশা বলা যায়। প্রিএম্পটিভ ও সমবায়মূলক হলো প্রসেস/থ্রেড শিডিউলিংয়ের দুটি প্রচলিত পদ্ধতি,অপারেটিং সিস্টেমের প্রসেস শিডিউলিংয়ের জন্য প্রিএম্পটিভ পদ্ধতি খুবই উপযোগী,এটি বিভিন্ন প্রসেসকে সময়খণ্ড বরাদ্দ করে,দীর্ঘ সময় সাড়া না দেওয়া প্রসেসের ক্ষেত্রে,এটি তার সম্পদ কেড়ে নিতে পারে,এমনকি জোর করে বন্ধও করতে পারে। সমবায়মূলক পদ্ধতিতে,প্রসেসকে স্বেচ্ছায় ও নিজ উদ্যোগে সম্পদ ছেড়ে দিতে হয়,এই শিডিউলিং পদ্ধতিতে,দীর্ঘ সময় চলা একটি থ্রেডের কারণে CPU প্রয়োজন এমন অন্য সব থ্রেড ”অনাহারে” থাকতে পারে। Java hotspot ভার্চুয়াল মেশিন প্রিএম্পটিভ শিডিউলিং ব্যবহার করে,তাই Java ভাষা শুরু থেকেই প্রিএম্পটিভ থ্রেড শিডিউলিং পদ্ধতি গ্রহণ করেছে। JDK 1.0-এ থ্রেড তৈরির প্রধান উপায় হলো Thread ক্লাস থেকে উত্তরাধিকার নেওয়া অথবা Runnable ইন্টারফেস বাস্তবায়ন করা,অবজেক্ট ইনস্ট্যান্সের start মেথড দিয়ে থ্রেড চালু করা হয়,সমান্তরালে চালানোর কোড run মেথডে রাখা হয়,থ্রেডগুলোর সহযোগিতামূলক যোগাযোগের জন্য stop/resume/suspend-এর মতো সরল ও কঠোর পদ্ধতি ব্যবহার করা হয়।

Java থ্রেডের অবস্থা ও অবস্থান্তরের চিত্র

stop/resume/suspend ধারণাগুলো কীভাবে বোঝানো যায়? মূল থ্রেড সরাসরি চাইল্ড থ্রেডের বন্ধ করা, বিরতি দেওয়া ও আবার চালানোর মেথড কল করতে পারে। ছোটবেলায় ওয়াকম্যান ব্যবহার করে থাকলে দেখেছেন, এতে তিনটি বোতাম থাকে: বন্ধ, বিরতি ও আবার চালানো। কল্পনা করুন, আপনি একসঙ্গে 3টি ওয়াকম্যান শুনছেন। তিনটি ওয়াকম্যান হলো তিনটি চাইল্ড থ্রেড, আর আপনি মূল থ্রেড; এই তিনটি যন্ত্র কখন চলবে বা থামবে, তা আপনি ইচ্ছেমতো নিয়ন্ত্রণ করতে পারেন।

এই ব্যবস্থায় একটি মারাত্মক সমস্যা আছে, তা হলো সহজেই ডেডলক ঘটতে পারে। কারণ থ্রেড A কোনো রিসোর্স লক করার পর তা ছাড়ার আগেই মূল থ্রেড তাকে স্থগিত করে দিতে পারে (suspend মেথড লক ছাড়ে না)। তখন থ্রেড B সেই রিসোর্স দখল করতে চাইলে, থ্রেড A পুনরায় চলতে শুরু করে (resume) রিসোর্স ছেড়ে দেওয়া পর্যন্ত অপেক্ষা করা ছাড়া উপায় থাকে না; তা না হলে সে কখনোই রিসোর্সটি পাবে না, ফলে ডেডলক ঘটে।

JDK 1.2

এই সংস্করণে বলপ্রয়োগমূলক stop/resume/suspend প্রক্রিয়া ব্যবহার নিষিদ্ধ করা হয়, তার পরিবর্তে wait/notify/sleep-এর মতো একাধিক থ্রেডের সমন্বয়ে কাজ করার পদ্ধতি নেওয়া হয়। উল্লেখ করার মতো বিষয় হলো, এই সংস্করণে AtomicityXXX অ্যাটমিক অবজেক্টগুলোর নকশা ইতিমধ্যেই তৈরি হয়েছিল, মূলত i++-এর অ-অ্যাটমিক হওয়ার সমস্যা সমাধানের জন্য। ThreadLocal ও Collections যুক্ত হওয়ায় একাধিক থ্রেড ব্যবহারের পদ্ধতি আরও বেড়ে যায়, এই দুটি প্রযুক্তির কারণে লেখক একে Java-এর টার্বোচার্জিং যুগ বলেন।

ThreadLocal

ThreadLocal হলো লক ব্যবহার না করে একাধিক থ্রেডে থ্রেড-অনিরাপদ অবজেক্ট ব্যবহারের একটি সমাধান। এটি “ব্যাংক অ্যাকাউন্ট বা মজুত বাড়ানো, কমানো”-জাতীয় সমস্যা সমাধান করতে পারে না, এটি “টুল” হিসেবে ব্যবহৃত ক্লাসের অনুলিপি তৈরি করে নিরাপদে “টুল”-এর মেথড চালাতে পারদর্শী। এর সাধারণ উদাহরণ SimpleDateFormat, ডেটাবেস সংযোগ ইত্যাদি। উল্লেখ করার মতো বিষয় হলো, এর নকশা খুবই চতুর, ভাবুন তো আপনাকে এটি নকশা করতে দিলে সাধারণ সরল চিন্তাটি হতো:ThreadLocal-এর মধ্যে একটি গ্লোবাল থ্রেড-নিরাপদ Map রাখা, যেখানে key হবে থ্রেড, value হবে ভাগ করে ব্যবহৃত অবজেক্ট। এই নকশার একটি অসুবিধা হলো মেমরি লিকের সমস্যা, কারণ আরও বেশি থ্রেড যোগ হওয়ার সঙ্গে সঙ্গে Map-টি সীমাহীনভাবে বড় হতে থাকবে, মেমরি লিক সমাধান করতে হলে থ্রেড শেষ হওয়ার সময় Map-টি পরিষ্কার করতে হবে, এর জন্য আবার GC-এর সক্ষমতা বাড়াতে হবে, স্পষ্টতই ব্যয়ের তুলনায় লাভ উপযুক্ত নয়। তাই ThreadLocal এমনভাবে নকশা করা হয়েছে যাতে Map-এর মালিক ThreadLocal না হয়ে স্বয়ং Thread হয়। key হলো ThreadLocal ভেরিয়েবল, value হলো মান। প্রতিটি Thread ব্যবহৃত ThreadLoacl-গুলো এর মধ্যে রাখে(অবশ্য এই নকশা থেকে আরও কিছু সমস্যা দেখা দেয়, এখানে সেগুলো আলোচনা করা হচ্ছে না, আগ্রহীরা নিজেরা খুঁজে দেখতে পারেন)。

Collections

এই সংস্করণে Collections ইউটিলিটি ক্লাস তৈরি করা হয়েছিল;এটি SynchronizedList-এর মতো কিছু থ্রেড নিরাপদ কালেকশন মোড়কবদ্ধ করে। তখন কেবল Hashtable、Vector、Stack ইত্যাদি থ্রেড নিরাপদ কালেকশন ছিল, তাই এর আবির্ভাবও যুগান্তকারী ছিল। Collections ইউটিলিটির মূল ভাবনা হলো, আমি তোমার থ্রেড অনিরাপদ কালেকশনকে মোড়ক দিয়ে থ্রেড নিরাপদ করে দেব, যাতে পুরোনো কোড উন্নত করতে বেশি সময় ব্যয় করতে না হয়;কালেকশন তৈরির সময় শুধু আমার দেওয়া পদ্ধতিতে তা আরম্ভ করলেই হবে। ব্যাপারটা অনেকটা গাড়ির টার্বোচার্জিং প্রযুক্তির মতো, যেখানে ইঞ্জিনের স্থানচ্যুতি অপরিবর্তিত রেখেই তার শক্তি ও টর্ক বাড়ানো হয়। Java-র টার্বোচার্জিং যুগ এসে গেল ^\_^

স্বয়ংক্রিয় গিয়ারের যুগ

JDK 5.0

সমবর্তী প্রক্রিয়াকরণের প্যাকেজ আমদানি

Doug Lea, চীনা নাম দাওগে·লি। তিনি যুক্তরাষ্ট্রের একজন বিশ্ববিদ্যালয় শিক্ষক, অসাধারণ দক্ষ একজন ব্যক্তিত্ব, J.U.C তাঁরই সৃষ্টি। JDK1.5-এর আগে প্রোগ্রামে সমকালীন প্রবেশের ক্ষেত্রে কোডের সমন্বয় নিয়ন্ত্রণ করতে আমরা কেবল synchronized ব্যবহার করতে পারতাম, তখন synchronized-এর কর্মক্ষমতা ভালোভাবে অনুকূলিত হয়নি, তাই কর্মক্ষমতা ভালো ছিল না, থ্রেড নিয়ন্ত্রণেও কেবল Object-এর wait ও notify মেথড ব্যবহার করা যেত। সেই সময় Doug Lea JCP-র কাছে JSR-166 প্রস্তাব জমা দেন, JSR-166 জমা দেওয়ার আগেই Doug Lea তিন বছরের বেশি সময় ধরে J.U.C প্যাকেজের মতো কার্যকারিতাসম্পন্ন কোড ব্যবহার করছিলেন, সেই কোডই ছিল J.U.C-এর প্রাথমিক রূপ।

J.U.C অ্যাটমিক অবজেক্ট, লক ও সহায়ক সরঞ্জামের সেট, থ্রেড পুল এবং থ্রেড সুরক্ষিত কন্টেইনারসহ কয়েকটি প্রধান শ্রেণির সরঞ্জাম দেয়। বিকাশকারীরা যেকোনো সক্ষমতা নমনীয়ভাবে ব্যবহার করে নিজেদের পণ্য তৈরি করতে পারেন; চাইলে ReentrantLock দিয়ে নিম্নস্তরের ফ্রেমওয়ার্ক গড়তে পারেন, আবার সরাসরি তৈরি সরঞ্জাম বা কন্টেইনার ব্যবহার করে ব্যবসায়িক কোডও লিখতে পারেন। ইতিহাসের দৃষ্টিতে দেখলে, 2004 সালে J.U.C-কে নিঃসন্দেহে “অত্যাধুনিক প্রযুক্তিপণ্য” বলা যায়। Java-র প্রসারে এটি অসামান্য অবদান রেখেছে। Java-র স্বয়ংক্রিয় গিয়ারের যুগ এসে গেল; স্বয়ংক্রিয় গিয়ারের গাড়ি যেমন চালকের দক্ষতার প্রয়োজন কমিয়ে দেয়, তেমনি J.U.C প্রোগ্রামারদের জন্য মাল্টিথ্রেডিং ব্যবহার অনেক সহজ করেছে। এটি একটি নতুন যুগের সূচনা করা পণ্য।

অবশ্য J.U.C-তেও কিছু খুঁত রয়েছে:

CPU-এর ব্যয় বেশি:স্পিনিং CAS দীর্ঘ সময় ধরে সফল না হলে,CPU-র ওপর অত্যন্ত বেশি খরচের চাপ পড়ে।

সমাধান: JUC-এর কিছু অংশে CAS স্পিনের সংখ্যা সীমিত করা হয়েছে, যেমন BlockingQueue-এর SynchronousQueue-তে।

ABA সমস্যা:কোনো মান প্রথমে A ছিল, পরে B হলো, তারপর আবার A হলো, তাহলে CAS পরীক্ষা করার সময় মনে হবে কোনো পরিবর্তন হয়নি, কিন্তু আসলে তা বদলেছে, এটিই ABA সমস্যা। অধিকাংশ ক্ষেত্রে ABA সমস্যা প্রোগ্রামের সমকালীন নির্বাহের সঠিকতায় প্রভাব ফেলে না।

সমাধান:প্রতিটি ভেরিয়েবলের সঙ্গে একটি সংস্করণ নম্বর যোগ করতে হবে, প্রতিবার পরিবর্তনের সময় 1 বাড়াতে হবে, অর্থাৎ A —> B —> A হয়ে যাবে 1A —> 2B —> 3A। Java এই সমস্যা সমাধানে AtomicStampedReference দিয়েছে। AtomicStampedReference [E,Integer] টাপলকে মোড়কবদ্ধ করে অবজেক্টে সংস্করণচিহ্ন(stamp)লাগায়, ফলে ABA সমস্যা এড়ানো যায়।

শুধু একটি ভাগাভাগি করা ভেরিয়েবলের অপারেশনের পারমাণবিকতা নিশ্চিত করতে পারে: CAS প্রক্রিয়া শুধু একটি ভেরিয়েবলের অপারেশনের অ্যাটমিকতা নিশ্চিত করে, পুরো কোড ব্লকের অ্যাটমিকতা নিশ্চিত করতে পারে না।

সমাধান:যেমন, 3টি ভেরিয়েবলকে একসঙ্গে অ্যাটমিকভাবে আপডেট করা নিশ্চিত করতে হলে Synchronized ব্যবহার করতেই হবে। একাধিক ভেরিয়েবলকে মোড়াতে AtomicReference ব্যবহারের কথাও ভাবা যায়; এভাবে একাধিক শেয়ার করা ভেরিয়েবলের পরিস্থিতি সামলানো যায়।

Java মেমরি মডেল স্পষ্ট করা

JDK-এর এই সংস্করণে Java মেমরি মডেলকে নতুন করে স্পষ্ট করা হয়েছিল, এর আগে প্রচলিত মেমরি মডেলগুলির মধ্যে ছিল ক্রমিক সামঞ্জস্যের মেমরি মডেল ও আগে-ঘটার মডেল। ক্রমিক সামঞ্জস্যের মডেলে প্রোগ্রাম চালানোর ক্রম এবং কোডে দেখানো ক্রম পুরোপুরি একই থাকে। আধুনিক বহু-কোরবিশিষ্ট এবং নির্দেশনা কার্যকরের অনুকূলনকারী CPU-র পক্ষে এটি নিশ্চিত করা খুব কঠিন। তাছাড়া, ক্রমিক সামঞ্জস্য নিশ্চিত করার বাধ্যবাধকতা কোড চালানোর সময় JVM-এর অনুকূলনের সুযোগকে গুরুতরভাবে সীমিত করে দেয়।

কিন্তু এই সংস্করণের JSR 133 স্পেসিফিকেশনে নির্ধারিত হ্যাপেন্স-বিফোর(Happens-before)সম্পর্ক নির্দেশনা কার্যকরের ক্রমকে নমনীয় করে:

একই থ্রেডের মধ্যে,কোড নির্বাহের ক্রম অনুযায়ী(অর্থাৎ কোডের অর্থগত ক্রম অনুযায়ী),আগের অপারেশন পরের অপারেশনের আগে ঘটে একটি monitor অবজেক্ট আনলক করার অপারেশন পরবর্তী সময়ে একই monitor অবজেক্ট লক করার অপারেশনের আগে ঘটে volatile ফিল্ডে লেখার অপারেশন পরবর্তী সময়ে সেই ফিল্ড পড়ার অপারেশনের আগে ঘটে থ্রেডের start অপারেশন(থ্রেড অবজেক্টের start() মেথড কল করা)সেই থ্রেডের অন্য যেকোনো অপারেশনের আগে ঘটে একটি থ্রেডের সব অপারেশন অন্য যেকোনো থ্রেড কর্তৃক এই থ্রেডে join() মেথড কল করার আগে ঘটে যদি A অপারেশন B-এর আগে ঘটে,এবং B অপারেশন C-এর আগে ঘটে,তাহলে A অপারেশন C-এর আগে ঘটে

মেমরি বরাদ্দের ক্ষেত্রে প্রতিটি থ্রেডের নিজস্ব কার্যকরী মেমরিকে মূল মেমরি থেকে আলাদা করে দেওয়া JVM-কে থ্রেডের ভেতরের নির্দেশ কার্যকর করার পদ্ধতি অপ্টিমাইজ করার প্রচুর সুযোগ দেয়। মূল মেমরির ভেরিয়েবলগুলি থ্রেডের কার্যকরী মেমরিতে কপি করে আলাদাভাবে প্রক্রিয়া করা যায়, কাজ শেষ হলে কোনো এক সময় ফলাফল মূল মেমরিতে ফিরিয়ে লেখা যায়: কিন্তু বিভিন্ন থ্রেডের মধ্যে তথ্যের সামঞ্জস্য কীভাবে নিশ্চিত করা হবে?JLS(Java Language Specification)যে উপায় দেয় তা হলো, স্বাভাবিক অবস্থায় যেকোনো মুহূর্তে তথ্যের সামঞ্জস্য নিশ্চিত করা যায় না, তবে বর্ধিত সেমান্টিক্সযুক্ত synchronized, volatile ও final কীওয়ার্ড ব্যবহার করে তথ্যের সামঞ্জস্য অর্জন করা যায়।

JDK 6.0-এ synchronized-এর অপ্টিমাইজেশন

“প্রজাতন্ত্রের জ্যেষ্ঠ সন্তান” হিসেবে পরিচিত synchronized কীওয়ার্ডটি 5.0 সংস্করণে ReentrantLock-এর কাছে আলোচনার কেন্দ্রে থাকার জায়গা হারিয়েছিল। এই সংস্করণে ঘুরে দাঁড়াতেই হতো, তাই JDK 6.0 লক নিয়ে কিছু অপ্টিমাইজেশন করে, যেমন লক স্পিনিং, লক অপসারণ, লক একত্রীকরণ, লাইটওয়েট লক, বায়াসড লক ইত্যাদি। এই অপ্টিমাইজেশন “সূক্ষ্ম ব্যবস্থাপনা” ধারণার একটি ব্যাখ্যা। অপ্টিমাইজেশনের আগে synchronized দিয়ে লক করা অবজেক্টের কেবল দুটি অবস্থা ছিল:লকহীন, লকযুক্ত(হেভিওয়েট লক)。 অপ্টিমাইজেশনের পরে লকের মোট 4টি অবস্থা হয়, নিম্ন থেকে উচ্চস্তরের ক্রমে:লকহীন, বায়াসড লক, লাইটওয়েট লক, হেভিওয়েট লক। প্রতিযোগিতার পরিস্থিতি অনুযায়ী এই অবস্থাগুলো ধীরে ধীরে উচ্চস্তরে যায়, কিন্তু নিম্নস্তরে ফিরতে পারে না, উদ্দেশ্য লক নেওয়া ও ছাড়ার দক্ষতা বাড়ানো(লেখকের মনে হয় আসলে বিষয়টি এত জটিল যে JVM-এর উন্নয়নকারীরাও পিছিয়ে গেছেন)。

এই অপ্টিমাইজেশন synchronized-কে মাথা উঁচু করে দাঁড়াতে দিল,এরপর থেকে তার কর্মক্ষমতা ReentrantLock-এর চেয়ে খারাপ বলার সুযোগ আর রইল না。তবে আসল নাটক তখনও বাকি,JDK 15-এ বায়াসড লক অবলুপ্ত ঘোষিত হলো(─.─||)。আমার মনে হয় synchronized-এর অসুবিধা হলো,এটি কেবল একটি কীওয়ার্ড,এর নিচের স্তরের কাজগুলো JVM সামলায়,অ্যাপ্লিকেশনে লক নেওয়ার সময় কোন পদ্ধতিটি সুবিধাজনক হবে,সেটা JVM-কে “আন্দাজ” করতে হয়。ReentrantLock-এর ব্যাপার আলাদা,এটি বিষয়টি সরাসরি প্রোগ্রামারের হাতে ছেড়ে দেয়,ন্যায্যতা চাইলে ফেয়ার লক ব্যবহার করুন,অন্যায্যতা চাইলে নন-ফেয়ার লক ব্যবহার করুন。নকশার দিক থেকে একে খানিকটা অলসতা বলা যায়,তবে এটি নমনীয়তাও দেয়。

JDK 7.0 Fork/Join ফ্রেমওয়ার্ক

Fork/Join-এর আবির্ভাবও ছিল বেশ অগ্রসর একটি প্রযুক্তির আগমন। এর মূল শক্তি হলো, এটি কাজকে পুনরাবৃত্তিমূলকভাবে ছোট ছোট ভাগে ভাঙতে এবং প্রতিটি কাজের ফল একত্র করতে পারে। কিন্তু এই প্রযুক্তি একই সঙ্গে পরিচিত ও অপরিচিত। পরিচিত, কারণ এটি নানা জায়গায় ব্যবহৃত হয়, যেমন সামনে JDK8.0 অংশে আলোচিত CompletableFuture ও Stream;অপরিচিত, কারণ ব্যবসায়িক সফটওয়্যার তৈরির সময় আমরা সম্ভবত এটি খুব কমই ব্যবহার করি।

এমনকি কেউ কেউ এটিকে প্রায় অকেজোও মনে করেন। লেখকের মতে, আপনি যদি ব্যবসায়িক চাহিদা নিয়ে উন্নয়নের কাজ করেন, তবে এটি তেমন কাজে আসে না, কারণ প্রায় ব্যবহারই হয় না; বিপুল ডেটার জন্য ডেটা ওয়্যারহাউসের সরঞ্জাম আছে, অন্য ক্ষেত্রে থ্রেড পুল দিয়ে কাজ চালানো যায়। আপনি যদি মিডলওয়্যার ফ্রেমওয়ার্ক লেখার কাজ করেন, তবে এটি কাজে লাগতে পারে। চীনা ভাষার ইন্টারনেটে খুব কম মানুষই এই প্রযুক্তি নিয়ে প্রশ্ন তোলেন, কিন্তু বিদেশে ইতিমধ্যে আলোচনা হচ্ছে; আগ্রহীরা সরাসরি গিয়ে পড়তে পারেন Java-র Fork-Join ফ্রেমওয়ার্ক কি বিকল?

JDK 8.0

এই সংস্করণের প্রকাশ Java-র জন্য যুগান্তকারী ছিল, এতটাই যে বর্তমানে বিশ্বজুড়ে চলমান Java প্রোগ্রামের অর্ধেকেরও বেশি এই সংস্করণ ব্যবহার করে। তবে মাল্টিথ্রেডিং-সংক্রান্ত পরিবর্তনগুলো JDK5.0-এর মতো বৈপ্লবিক ছিল না। কিছু অ্যাটমিক অবজেক্ট যোগ করার পাশাপাশি এই সংস্করণের সবচেয়ে উল্লেখযোগ্য পরিবর্তন ছিল নিচের দুটি।

CompletableFuture

ইন্টারনেটে CompletableFuture নিয়ে প্রচুর পরিচিতিমূলক লেখা আছে, যার বেশির ভাগেই এর কার্যপ্রণালি এবং ব্যবহার ব্যাখ্যা করা হয়। কিন্তু একটি প্রশ্ন লেখকের কাছে বরাবরই অস্পষ্ট ছিল:এত থ্রেড পুল টুল থাকা সত্ত্বেও CompletableFuture কেন এল, এটি কোন সমস্যার সমাধান করে?এর মূল প্রতিযোগিতামূলক সুবিধা আসলে কী?আমার বিশ্বাস, বিষয়টি নিয়ে ভাবলে আপনার মনেও এই প্রশ্ন উঠবে; চিন্তা নেই, লেখক ইতিমধ্যেই আপনার জন্য উত্তর খুঁজে পেয়েছেন।

উপসংহার:CompletableFuture-এর মূল প্রতিযোগিতামূলক শক্তি হলোকাজের সমন্বিত বিন্যাস।CompletableFuture, Future ইন্টারফেসের বৈশিষ্ট্য উত্তরাধিকারসূত্রে পেয়েছে, যেমন সমসাময়িকভাবে কাজ সম্পাদনের ক্ষমতা; এসব ক্ষমতার বিকল্প রয়েছে। কিন্তু এর কাজের সমন্বয় করার ক্ষমতার বিকল্প নেই; এর মূল API-তে কাজের শৃঙ্খল তৈরি, কাজের ফলাফল একত্র করা ইত্যাদি সবই কাজের সমন্বয়ের জন্য নকশা করা হয়েছে। তাই JDK এই সংস্করণে ফ্রেমওয়ার্কটি এনেছে মূলত ব্যবসায়িক সফটওয়্যার উন্নয়নে ক্রমশ বেশি সমস্যাজনক হয়ে ওঠা কাজের সমন্বয়ের চাহিদা মেটাতে।

সবশেষে আরেকটি কথা, CompletableFuture-এর অন্তর্নিহিত বাস্তবায়নে Fork/Join ফ্রেমওয়ার্ক ব্যবহৃত হয়।

Stream

《架构整洁之道》(পরিচ্ছন্ন স্থাপত্যের পথ)বইতে তিন ধরনের প্রোগ্রামিং প্যারাডাইমের উল্লেখ আছে, কাঠামোবদ্ধ প্রোগ্রামিং(প্রক্রিয়াভিত্তিক প্রোগ্রামিং), অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং এবং ফাংশনাল প্রোগ্রামিং। Stream হলো Java ভাষায় ফাংশনাল প্রোগ্রামিংয়ের একটি প্রকাশরূপ। লেখকের মতে, জুনিয়র প্রোগ্রামার থেকে মধ্যম স্তরে উন্নীত হওয়ার পথে Stream আয়ত্ত করা অপরিহার্য। প্রথমবার Stream-এর সঙ্গে পরিচিত হলে নিশ্চয়ই খুব অস্বস্তি লাগবে, কিন্তু অভ্যস্ত হয়ে গেলে প্রোগ্রামিংয়ের পদ্ধতি নিয়ে ভাবনার একটি নতুন পথ খুলে যাবে। সফটওয়্যার উন্নয়নের কাজে আমরা প্রায়ই তিনটি ধারণা গুলিয়ে ফেলি, ফাংশনাল প্রোগ্রামিং, Stream এবং Lambda এক্সপ্রেশন, এবং সব সময় মনে করি তিনটিই একই কথা বলে। নিচে লেখকের উপলব্ধি দেওয়া হলো:

•ফাংশনাল প্রোগ্রামিং একটি প্রোগ্রামিং চিন্তাধারা, বিভিন্ন প্রোগ্রামিং ভাষায় এর প্রয়োগ রয়েছে

•Stream হলো JDK8.0-এর একটি নতুন বৈশিষ্ট্য;একে নতুন একটি ধারণা তৈরি করা হয়েছে বলেও বোঝা যায়, যার উদ্দেশ্য ফাংশনাল প্রোগ্রামিংয়ের ভাবনার সঙ্গে মানিয়ে চলা;Stream-এর মাধ্যমে কালেকশন ক্লাসে ফাংশনাল প্রোগ্রামিং বাস্তবায়ন করা যায়

•Lambda এক্সপ্রেশন(lambda expression)একটি নামহীন ফাংশন, যার সাহায্যে ফাংশনাল প্রোগ্রামিং আরও সংক্ষিপ্ত ও দক্ষভাবে প্রকাশ করা যায়

এত কিছু বলার পর, Stream-এর সঙ্গে মাল্টিথ্রেডিংয়ের সম্পর্ক কী?Stream-এর সংশ্লিষ্ট সমান্তরাল পদ্ধতিগুলোর অন্তর্নিহিত বাস্তবায়নে Fork/Join ফ্রেমওয়ার্ক ব্যবহৃত হয়। 《Effective Java》-তে এ বিষয়ে একটি পরামর্শ রয়েছে, “Stream-এর সমান্তরাল প্রক্রিয়াকরণ সতর্কভাবে ব্যবহার করুন”; কারণ সব সমান্তরাল কাজ একটি সাধারণ Fork/Join পুলে চলে, একটি পাইপলাইনে অস্বাভাবিকতা ঘটলে তা অন্য অসংশ্লিষ্ট অংশের কর্মক্ষমতাও ক্ষতিগ্রস্ত করতে পারে।

JDK 9.0

লক নিয়ে প্রতিযোগিতা সামলানোর ব্যবস্থা উন্নত করা

লক নিয়ে প্রতিদ্বন্দ্বিতা বহু Java বহুথ্রেড অ্যাপ্লিকেশনের কর্মক্ষমতা সীমিত করে,নতুন লক প্রতিদ্বন্দ্বিতা ব্যবস্থা Java অবজেক্ট মনিটরের কর্মক্ষমতা উন্নত করেছে,এবং বিভিন্ন বেঞ্চমার্ক পরীক্ষায় তা যাচাই হয়েছে(যেমন Volano),এ ধরনের পরীক্ষা JVM-এর সর্বোচ্চ থ্রুপুট অনুমান করতে পারে। বাস্তবে, নতুন লক প্রতিদ্বন্দ্বিতা ব্যবস্থা 22 ধরনের ভিন্ন বেঞ্চমার্ক পরীক্ষাতেই চমৎকার ফল পেয়েছে। নতুন ব্যবস্থাটি Java 9-এ প্রয়োগ করা গেলে, অ্যাপ্লিকেশনের কর্মক্ষমতা অনেক বাড়বে। সহজভাবে বললে,একাধিক থ্রেড লকের জন্য প্রতিদ্বন্দ্বিতা করলে,অপ্টিমাইজেশনের আগে:পরে আসা সব থ্রেড একই মানক প্রক্রিয়ায় লকের জন্য অপেক্ষা করে। অপ্টিমাইজেশনের পরে:JVM কিছু অপ্টিমাইজযোগ্য পরিস্থিতি শনাক্ত করলে পরে আসা থ্রেডকে সরাসরি “VIP পথ” দিয়ে লক দখলের সুযোগ দেয়।

বিস্তারিত ব্যাখ্যার জন্য দেখুন: লক পাওয়ার প্রতিযোগিতার ব্যাখ্যা – কর্মদক্ষতাভিত্তিক দৃষ্টিভঙ্গি

রিঅ্যাকটিভ স্ট্রিম

প্রতিক্রিয়াশীল স্ট্রিম (Reactive Streams) হলো নন-ব্লকিং ব্যাকপ্রেশারের মাধ্যমে অ্যাসিঙ্ক্রোনাস ডেটা স্ট্রিম প্রক্রিয়া করার একটি মানদণ্ড। প্রয়োজনীয় ক্রিয়া ও সত্তা বর্ণনা করতে এটি ন্যূনতম কিছু ইন্টারফেস, মেথড ও প্রোটোকল সরবরাহ করে।

নন-ব্লকিং ব্যাকপ্রেশার কী? 背压 হলো back pressure-এর সংক্ষিপ্ত রূপ। সহজভাবে বললে, প্রোডিউসার কনজিউমারের কাছে ডেটা পাঠায়, কনজিউমার আর প্রক্রিয়াকরণ করতে না পারলে প্রোডিউসারকে জানায়, তখন প্রোডিউসার উৎপাদনের গতি কমায়। এই ব্যবস্থা ব্লকিং পদ্ধতিতে বাস্তবায়ন করা সবচেয়ে সহজ, অর্থাৎ ডেটা পাঠানোর সময়ই চাপের তথ্য সরাসরি ফেরত দেওয়া। নন-ব্লকিং পদ্ধতিতে বাস্তবায়ন করলে নকশার জটিলতা বাড়ে, সেই সঙ্গে কর্মক্ষমতাও বাড়ে। পুনশ্চ:মনে হয় 背压 অনুবাদটি ভালো হয়নি, শুধু অক্ষর দেখে অর্থ বোঝা যায় না। 反压(বিপরীত চাপ)কি আরও ভালো হবে^\_^

উপভোক্তা বিপুল সম্পদচাপের(pressure)কারণে বিপর্যস্ত হয়ে পড়তে পারে—এই সমস্যা সমাধানে ডেটা প্রবাহের গতি নিয়ন্ত্রণ করা দরকার, অর্থাৎ প্রবাহ নিয়ন্ত্রণ(flow control), যাতে দ্রুত ডেটা প্রবাহ গন্তব্যকে অতিরিক্ত চাপে অচল না করে। তাই বিপরীত চাপ, অর্থাৎ ব্যাকপ্রেশার(back pressure)দরকার; উৎপাদক ও উপভোক্তার মধ্যে একটি ব্যাকপ্রেশার ব্যবস্থা বাস্তবায়নের মাধ্যমে পারস্পরিক কাজের সমন্বয় করতে হয়। এই ব্যবস্থা অ্যাসিঙ্ক্রোনাস ও নন-ব্লকিং হওয়া আবশ্যক; সিঙ্ক্রোনাস ও ব্লকিং হলে, উপভোক্তা ডেটা প্রক্রিয়াকরণের সময় উৎপাদককে অপেক্ষা করতে হবে, ফলে কর্মক্ষমতার সমস্যা হবে।

Reactive Streams একগুচ্ছ সত্তা, ইন্টারফেস ও আন্তঃপরিচালনা পদ্ধতি সংজ্ঞায়িত করে নন-ব্লকিং ব্যাকপ্রেশার বাস্তবায়নের মানদণ্ড দেয়। তৃতীয় পক্ষ এই মানদণ্ড অনুসরণ করে নির্দিষ্ট সমাধান বাস্তবায়ন করে; পরিচিত উদাহরণের মধ্যে রয়েছে Reactor, RxJava, Akka Streams, Ratpack ইত্যাদি।

JDK 10 থ্রেড-স্থানীয় নিয়ন্ত্রণ

Safepoint এবং তার সীমাবদ্ধতা:

Safepoint হলো Hotspot JVM-এর এমন একটি ব্যবস্থা, যা সমস্ত অ্যাপ্লিকেশনকে থামায়। JVM-কে কিছু নিম্নস্তরের কাজ করতে হলে Stop The World করতে হয়, অর্থাৎ অ্যাপ্লিকেশনের সব থ্রেড থামাতে হয়। তবে সরাসরি জোর করে থামানো যায় না;বরং অ্যাপ্লিকেশন থ্রেডকে একটি নির্দেশসংকেত পাঠিয়ে জানানো হয়, এখন তোমার থামার সময়। তখন অ্যাপ্লিকেশন থ্রেড চলতে চলতে কোনো Safepoint-এ পৌঁছালে নির্দেশ মেনে সাড়া দেয়। এ কারণেই একে Safepoint বলা হয়। নামের সঙ্গে safe যোগ করার উদ্দেশ্য হলো জোর দিয়ে বলা যে JVM এখন সমগ্র ব্যবস্থাজুড়ে কিছু নিরাপদ কাজ করবে, তাই এই বিন্দুকে safe বলা হয়েছে।

সমগ্র ব্যবস্থার নিরাপত্তাসংক্রান্ত কাজগুলির মধ্যে রয়েছে: 1、আবর্জনা সংগ্রহের জন্য বিরতি 2、কোডের অপ্টিমাইজেশন প্রত্যাহার(Code deoptimization)。 3、কোড ক্যাশ ফ্লাশ করা। 4、ক্লাস ফাইল পুনঃসংজ্ঞায়নের সময়(Class redefinition, যেমন হট আপডেট বা instrumentation)。 5、বায়াসড লক প্রত্যাহার(Biased lock revocation)。 6、বিভিন্ন ডিবাগ কার্যক্রম(যেমন: ডেডলক পরীক্ষা অথবা stacktrace dump ইত্যাদি)。

তবে,সব থ্রেডকে নিকটতম safepoint-এ থামাতেই বেশ সময় লাগে। আর সব থ্রেড থামিয়ে দেওয়াটা কি একটু বেশি হঠকারী ও একতরফা মনে হয় না। এ জন্য Java10 এমন একটি পদ্ধতি চালু করে যাতে সব থ্রেড থামাতে হয় না,সেটি হলো থ্রেডভিত্তিক স্থানীয় নিয়ন্ত্রণ(Thread Local Handshake)。

যেমন নিচের পরিস্থিতিগুলোতে সব থ্রেড থামানোর প্রয়োজন হয় না: 1、পক্ষপাতযুক্ত লক প্রত্যাহার। এই কাজের জন্য শুধু একটি থ্রেড থামালেই পক্ষপাতযুক্ত লক প্রত্যাহার করা যায়, সব থ্রেড থামাতে হয় না। 2、বিভিন্ন ধরনের সার্ভিসেবিলিটি অনুসন্ধানের ফলে VM-এর সামগ্রিক বিলম্বের প্রভাব কমানো; যেমন বহু Java থ্রেড থাকা VM-এর সব থ্রেডের স্ট্যাক ট্রেস সংগ্রহ করা ধীরগতির কাজ হতে পারে। 3、সংকেতের (signals) ওপর নির্ভরতা কমিয়ে আরও নিরাপদ স্ট্যাক ট্রেস স্যাম্পলিং করা। 4、তথাকথিত অসমমিত Dekker সমন্বয় কৌশল ব্যবহার করে Java থ্রেডের সঙ্গে হ্যান্ডশেকের মাধ্যমে কিছু মেমরি ব্যারিয়ার দূর করা। যেমন, G1 ও CMS-এ ব্যবহৃত “শর্তাধীন কার্ড চিহ্নিতকরণ কোড” (conditional card mark code)-এর আর “মেমরি ব্যারিয়ার” নামের জিনিসটির প্রয়োজন হবে না। ফলে G1-এর “রাইট ব্যারিয়ার (write barrier)” অপ্টিমাইজ করা যাবে এবং “মেমরি ব্যারিয়ার” এড়ানোর চেষ্টা করা শাখাগুলোও সরানো যাবে।

JDK 15 বায়াসড লক নিষ্ক্রিয় করে এবং অবচয়িত ঘোষণা করে

বায়াসড লক কেন অবলুপ্ত করা হচ্ছে?অতীতে বায়াসড লক কর্মক্ষমতায় যে উন্নতি এনেছিল,এখন তা আর ততটা স্পষ্ট নয়। যেসব অ্যাপ্লিকেশন বায়াসড লক থেকে উপকৃত হয়,সেগুলো প্রায়ই পুরোনো Java কালেকশন API(JDK 1.1)ব্যবহার করে,এই API-গুলো(Hashtable এবং Vector)প্রতিবার অ্যাক্সেসের সময় সিঙ্ক্রোনাইজ করে। JDK 1.2 একক থ্রেডের জন্য সিঙ্ক্রোনাইজেশনবিহীন কালেকশন(HashMap এবং ArrayList)এনে দেয়,JDK 1.5 একাধিক থ্রেডের জন্য আরও কার্যকর সমসাময়িক ডেটা স্ট্রাকচার চালু করে। অর্থাৎ,কোড হালনাগাদ করে নতুন ক্লাস ব্যবহার করলে,অপ্রয়োজনীয় সিঙ্ক্রোনাইজেশনের কারণে বায়াসড লক থেকে সুবিধা পাওয়া অ্যাপ্লিকেশনগুলোর কর্মক্ষমতা অনেক বাড়তে পারে। এ ছাড়া,থ্রেড পুলের কিউ ও কর্মী থ্রেডকে ঘিরে তৈরি অ্যাপ্লিকেশনগুলোর কর্মক্ষমতা সাধারণত বায়াসড লক নিষ্ক্রিয় করলে আরও ভালো হয়।

নিচে Hashtable ও Vector ব্যবহার করা API দিয়ে বাস্তবায়ন দেওয়া হলো: java.lang.Classloader Vector ব্যবহার করে java.util.Properties extends Hashtable java.security.Provider extends Properties java.net.URL Hashtable ব্যবহার করে java.net.URConnection Hashtable ব্যবহার করে java.util.ZipOutputStream Vector ব্যবহার করে javax.management.timer.TimerMBean ইন্টারফেসে Vector রয়েছে

স্বয়ংচালিত গাড়ির যুগ

ভার্চুয়াল থ্রেড Java-কে স্বয়ংচালিত যুগে নিয়ে এসেছে। অনেক ভাষায় “ভার্চুয়াল থ্রেড”-এর মতো প্রযুক্তি রয়েছে, যেমন Go、C#、Erlang、Lua ইত্যাদি; তারা একে “কোরুটিন” বলে। এবার Java কোনো নতুন কীওয়ার্ড যোগ করেনি, এমনকি নতুন কোনো ধারণাও যোগ করেনি; goroutine বা কোরুটিনের তুলনায় ভার্চুয়াল থ্রেড বোঝা অনেক সহজ, নাম দেখলেই মোটামুটি বোঝা যায় এটি কী করছে।

JDK 19 ভার্চুয়াল থ্রেড

প্রচলিত Java-তে থ্রেড মডেল ও অপারেটিং সিস্টেমের থ্রেডের মধ্যে 1:1 সম্পর্ক থাকে। থ্রেড তৈরি করা ও এক থ্রেড থেকে অন্যটিতে স্যুইচ করার খরচ অনেক বেশি, আর অপারেটিং সিস্টেমের সীমাবদ্ধতার কারণে কেবল সীমিতসংখ্যক থ্রেড তৈরি করা যায়। একসঙ্গে বিপুলসংখ্যক অনুরোধ এলে প্রতিটির জন্য একটি থ্রেড তৈরি করা সম্ভব হয় না। থ্রেড পুল ব্যবহার করে সমস্যাটি কিছুটা লাঘব করা যায়; এতে থ্রেড তৈরির খরচ কমে, কিন্তু থ্রেডের সংখ্যাও বাড়ানো যায় না। ধরা যাক, সমসাময়িক অনুরোধের সংখ্যা 2000, অথচ থ্রেড পুলে মাত্র 1000টি থ্রেড আছে। তাহলে একই মুহূর্তে কেবল 1000টি অনুরোধ প্রক্রিয়া করা যাবে; বাকি 1000টি অনুরোধ তখন প্রক্রিয়া করা যাবে না। সেগুলো প্রত্যাখ্যান করা যেতে পারে, অথবা কোনো থ্রেড খালি হওয়া পর্যন্ত অপেক্ষায় রাখা যেতে পারে।

ভার্চুয়াল থ্রেডের আগের সমাধান ছিল অ্যাসিঙ্ক্রোনাস পদ্ধতি ব্যবহার করা। অনেক ফ্রেমওয়ার্ক ইতিমধ্যেই অ্যাসিঙ্ক্রোনাস পদ্ধতির সমসাময়িক প্রোগ্রামিং বাস্তবায়ন করেছে (যেমন Spring5-এর Reactor), যেখানে থ্রেড ভাগাভাগির মাধ্যমে আরও বেশি প্রাপ্যতা অর্জন করা হয়। এর নীতি হলো, থ্রেড ভাগাভাগির মাধ্যমে থ্রেড বদলের সংখ্যা ও খরচ কমানো এবং একই সঙ্গে ব্লকিং এড়ানো। শুধু প্রোগ্রাম চলার সময় থ্রেড ব্যবহৃত হয়; প্রোগ্রামকে অপেক্ষা করতে হলে সেটি থ্রেড দখল করে রাখে না। অ্যাসিঙ্ক্রোনাস পদ্ধতিতে সত্যিই বেশ কিছু উন্নতি হয়, তবে অসুবিধাও আছে। বেশির ভাগ অ্যাসিঙ্ক্রোনাস ফ্রেমওয়ার্ক চেইন আকারে কোড লেখার পদ্ধতি ব্যবহার করে, প্রোগ্রামকে অনেকগুলো ধাপে ভাগ করে এবং প্রতিটি ধাপ আলাদা থ্রেডে চলতে পারে। তখন পরিচিত ThreadLocal ও সমসাময়িক প্রোগ্রামিং-সংক্রান্ত অন্যান্য API আর ব্যবহার করা যায় না, করলে ভুল হতে পারে। প্রোগ্রামিংয়ের ধরনেও বড় পরিবর্তন আসে; এটি প্রচলিত ধরন থেকে অনেক বেশি জটিল এবং শিখতে খরচ বেশি। প্রকল্পের অনেক বিদ্যমান মডিউলকেও অ্যাসিঙ্ক্রোনাস পদ্ধতির উপযোগী করে বদলাতে হতে পারে।

ভার্চুয়াল থ্রেডের বাস্তবায়ন কিছু অ্যাসিঙ্ক্রোনাস ফ্রেমওয়ার্কের মতোই;এতেও থ্রেড ভাগাভাগি করা হয়, তাই পুল তৈরির প্রয়োজন হয় না। ব্যবহারের সময় ভার্চুয়াল থ্রেডকে অফুরন্ত ধরে নিতে পারেন;যত খুশি তৈরি করুন, সমস্যা হবে কি না তা নিয়ে ভাবতে হবে না। শুধু তা-ই নয়, ভার্চুয়াল থ্রেড ডিবাগ করা যায় এবং Java-সংশ্লিষ্ট পর্যবেক্ষণ সরঞ্জামও এটি সমর্থন করে, যা খুবই গুরুত্বপূর্ণ। ভার্চুয়াল থ্রেড আপনার প্রোগ্রামের মেমরি ব্যবহার অনেক কমিয়ে দেবে;Web Servers-এর মতো সব IO-নির্ভর অ্যাপ্লিকেশন একই হার্ডওয়্যারে IO থ্রুপুট অনেক বাড়াতে পারবে। আগে 1G মেমরিতে একসঙ্গে 1000টি অ্যাক্সেস সামলানো যেত;ভার্চুয়াল থ্রেড ব্যবহারের পর, আনুষ্ঠানিক বক্তব্য অনুযায়ী, সহজেই 100 লাখ সমসাময়িক অনুরোধ সামলানো সম্ভব। নির্দিষ্ট ব্যবসায়িক পরিস্থিতিতে তা সামলানো যাবে কি না, তা অবশ্য লোড টেস্টে দেখতে হবে;তবে কিছুটা কম ধরে নিলেও 10 লাখ সহজেই সম্ভব হওয়ার কথা, আর এর জন্য আপনাকে কোনো মূল্য দিতে হবে না, হয়তো কোডও বদলাতে হবে না। কারণ ভার্চুয়াল থ্রেড আপনাকে প্রচলিত প্রোগ্রামিং ধরন, অর্থাৎ প্রতি অনুরোধে একটি থ্রেডের মডেল বজায় রাখতে দেয়;সাধারণ থ্রেডের মতোই ভার্চুয়াল থ্রেড ব্যবহার করলে প্রোগ্রামে খুব সামান্য পরিবর্তন দরকার হয়। ভার্চুয়াল থ্রেড কোনো নতুন সিনট্যাক্সও আনেনি, তাই শেখা ও স্থানান্তরের খরচ অত্যন্ত কম বলা যায়।

উল্লেখযোগ্য বিষয় হলো, ভার্চুয়াল থ্রেডের অন্তর্নিহিত ব্যবস্থায় এখনও Fork/Join ফ্রেমওয়ার্ক ব্যবহৃত হয়।


মন্তব্য

পাঠানো হচ্ছে

জনপ্রিয় লেখা

আপনার থিম বাছুন

আপনার থিম পুরো সাইটে প্রযোজ্য হবে।