ব্যবহারিক মাল্টিথ্রেডিং বেস্ট প্র্যাকটিস: Java সংস্করণ — ভার্চুয়াল থ্রেড যুগের রীতি
· Go Komura · মাল্টিথ্রেডিং, Java, ব্যবসায়িক অ্যাপ্লিকেশন, বাগ তদন্ত, ডিজাইন
«Java-তে ব্যবসায়িক ব্যাচ কাজ সমান্তরাল করতে চাই।» «আমাদের Spring ওয়েব অ্যাপে শেয়ার করা ক্যাশ মাঝে মাঝে নষ্ট হয়।» «new Thread-এ ভরা পুরনো Swing অ্যাপ উত্তরাধিকার পেয়েছি।» — Java JDK 1.0 থেকে ভাষায় মাল্টিথ্রেডিং বসিয়েছে, java.util.concurrent টুলবক্সে পরিণত হয়েছে, আর JDK 21-এর ভার্চুয়াল থ্রেড দিয়ে সমকালীন প্রোগ্রামিংয়ের প্রচলিত জ্ঞান আবার লিখেছে। টুলসেট এত সমৃদ্ধ বলে কোন টুল বেছে নেবেন তা-ই ডিজাইনের গুণ হয়ে দাঁড়ায়।
এই নিবন্ধ মাল্টিথ্রেডিং-অনুশীলন সিরিজের Java সংস্করণ। Java-তে ব্যবসায়িক সিস্টেম, ব্যাচ কাজ ও সার্ভার অ্যাপ লেখা ডেভেলপারদের লক্ষ্য করে এটি মাল্টিথ্রেডিং ডিজাইনের নীতি — থ্রেড সরাসরি তৈরি করবেন না, শেয়ার করা পরিবর্তনযোগ্য অবস্থা কমান, লকিংয়ে শৃঙ্খলা, অন্য কিছুর আগে থামানোর ডিজাইন — Java-এর টুলে নামিয়ে আনে (প্রধানত LTS রিলিজ, JDK 21 ও পরে), আর আগস্ট ২০২৬ পর্যন্ত প্রাথমিক সূত্রে ভার্চুয়াল-থ্রেড যুগের বাছাই ও Java-নিজস্ব ফাঁদ জড়ো করে। একা পড়ার মতো করে লেখা। অন্য ভাষায় একই নীতি সঙ্গী নিবন্ধে আছে: «.NET সংস্করণ», «C++ সংস্করণ», «C সংস্করণ»।
১. আগে উপসংহার
- ব্যবসায়িক কোডে
new Threadলিখবেন না — একই নিয়ম Java-তেও। কাজExecutorService-এ দিন, থ্রেডের জীবনচক্র লাইব্রেরিকে সামলাতে দিন।1 - প্রধানত I/O অপেক্ষা করা কাজ ভার্চুয়াল থ্রেডে পাঠান। JDK 21-এ সরকারি ভার্চুয়াল থ্রেড প্রতি কাজে একটা ব্যবহার হয় এবং কখনো পুল করা যাবে না। সমকালীনতা
Semaphoreদিয়ে সীমাবদ্ধ করুন, পুল আকারে নয়।23 - ভার্চুয়াল থ্রেড থ্রুপুটের টুল, হিসাব দ্রুত করার টুল নয়। CPU-বাউন্ড সমান্তরাল এখনও প্রায় কোর সংখ্যার প্ল্যাটফর্ম থ্রেডের কাজ — স্থির পুল বা parallel stream, আগের মতো।3
private finalলক অবজেক্টে লক করুন, অথবা নিবেদিতReentrantLock-এ।synchronized(this)ও প্রকাশ্য অবজেক্টে লক বাইরের কোডের সঙ্গে ধাক্কা খেতে পারে। সময়সীমাসহ অধিগ্রহণ (tryLock) লাগলেReentrantLock।- JDK 21-23-এ
synchronizedব্লকের ভেতর ব্লক ভার্চুয়াল থ্রেড পিন করত; JDK 24 (JEP 491) ঠিক করেছে। পুরনো সতর্কতা আর বর্তমান বাস্তব আলাদা রাখুন।34 volatileদৃশ্যমানতা ও ক্রম নিশ্চিত করে, পরমাণুত্ব নয়। কাউন্টারেAtomicInteger/LongAdder, যৌগিক অবস্থায় লক।5- Interruption দিয়ে সহযোগী থামানো থ্রেড থামানোর একমাত্র সঠিক উপায়।
Thread.stop/suspend/resumeএখনUnsupportedOperationExceptionছোড়ে।InterruptedExceptionগিলবেন না — অবস্থা ফেরান বা উপরে ছুড়ুন।6 ExecutorServiceদুই-ধাপ প্যাটার্নে থামান: shutdown → awaitTermination → shutdownNow।shutdownNowbest-effort (মানক বাস্তবায়ন interruption দিয়ে), তাই ধরে নেয় আপনার কাজ interruption-এ সাড়া দেয়।1- Swing-এর UI একান্ত EDT-এর (Event Dispatch Thread)। অন্য থ্রেড থেকে আপডেট
SwingUtilities.invokeLaterদিয়ে চান।7
২. মাল্টিথ্রেডিং কেন কঠিন — রেস কন্ডিশন, ডেডলক ও মেমরি মডেল
মাল্টিথ্রেডিং যে সমস্যা আনে, ভাষা যাই হোক, সেদ্ধ করলে দুই রকম।
রেস কন্ডিশন এমন বাগ যেখানে ফল নির্ভর করে একাধিক থ্রেড কোন ক্রমে নির্দিষ্ট কোডে পৌঁছায়। ক্লাসিক উদাহরণ শেয়ার করা কাউন্টার: এক অভিব্যক্তি count++ আসলে তিন ধাপে ভাঙে — পড়ুন, যোগ করুন, ফিরিয়ে লিখুন। দুই থ্রেড একসঙ্গে এই তিন ধাপে ঢুকলে এক থ্রেডের বৃদ্ধি অন্যের লেখায় মুছে হারায়। প্রতি চালানে ফল বদলায়, কোন ফল পাবেন অনুমান করা যায় না।
sequenceDiagram
participant A as থ্রেড A
participant M as শেয়ার করা চলক count
participant B as থ্রেড B
Note over M: count = 10
A->>M: পড়া (10)
B->>M: পড়া (10)
A->>A: স্থানীয় যোগ (11)
B->>B: স্থানীয় যোগ (11)
A->>M: ফিরিয়ে লেখা (11)
B->>M: ফিরিয়ে লেখা (11)
Note over M: দুই বৃদ্ধি ঘটেছে,<br/>কিন্তু count = 11 — থ্রেড A-এর বৃদ্ধি হারিয়েছে
চিত্র ১: ক্লাসিক রেস কন্ডিশন যেখানে শেয়ার করা কাউন্টারের বৃদ্ধি হারায়। count++-এর তিন ধাপের মাঝে অন্য থ্রেড ঢুকলে শেষ লেখা অন্যটিকে মুছে দেয়
ডেডলক এমন অবস্থা যেখানে দুই থ্রেড প্রত্যেকে অন্যের ধরা লকের অপেক্ষা করে, তাই কেউ এগোতে পারে না। থ্রেড A লক ১ ধরে লক ২-এর অপেক্ষা করে; থ্রেড B লক ২ ধরে লক ১-এর অপেক্ষা করে — এটুকুই দুজনকে চিরকাল থামানোর জন্য যথেষ্ট।
flowchart LR
A["থ্রেড A<br/>লক ১ ধরে আছে"] -->|"লক ২ ছাড়ার অপেক্ষা"| B["থ্রেড B<br/>লক ২ ধরে আছে"]
B -->|"লক ১ ছাড়ার অপেক্ষা"| A
চিত্র ২: ডেডলকের বৃত্তাকার অপেক্ষা। অপেক্ষার তীর লুপ বানানোর মুহূর্তে সেই লুপের সব থ্রেড চিরকাল থামে
দুটোই সময়নির্ভর। যে ইন্টারলিভিং ডেভেলপমেন্ট মেশিনে কয়েক হাজার চালানে একবার দেখা যায়, ভিন্ন কোর সংখ্যা ও ভিন্ন লোডের প্রোডাকশন সার্ভারে প্রতিদিন ঘটতে পারে। «ডিবাগার লাগালে আর পুনরুৎপাদন হয় না» আর «লগ যোগ করলে মিলিয়ে যায়» দুটোই কারণ পর্যবেক্ষণ সময় বদলায় — রেস বাগের ক্লাসিক আচরণ। তাই এই নিবন্ধের সব নীতি সঠিকভাবে সিঙ্ক্রোনাইজ করার আগে সিঙ্ক্রোনাইজেশন লাগে এমন জায়গা কমানোর দিকে মুখ করে।
২.১. Java-নিজস্ব ভিত্তি — মেমরি মডেল ও happens-before
তার ওপরে, Java-নিজস্ব হলো শেয়ার করা ডেটা কেমন দেখা যায় তা Java Memory Model (JMM)-এর happens-before সম্পর্ক দিয়ে সংজ্ঞায়িত।
সিঙ্ক্রোনাইজেশন ছাড়া শেয়ার করা চলক পড়া-লেখা C++-ধাঁচের «অসংজ্ঞায়িত আচরণ» হয় না, তবে বৈধভাবে পুরনো মান দেখা যেতেই পারে, বা লেখা ক্রমবহির্ভূত দেখা যেতে পারে। মেমরি সামঞ্জস্য ত্রুটি যেখানে «লুপ boolean পতাকা দেখছে, কিন্তু অন্য থ্রেডের বদলানো মান কখনো দেখা যায় না» JMM-এর অনুমোদিত আচরণ, JVM বাগ নয়।5 এর বিরুদ্ধে রক্ষা করে যে ব্যবস্থা happens-before তৈরি করে — synchronized, volatile, আর java.util.concurrent-এর ক্লাস। সমকালীন কালেকশন ঠিকভাবে ব্যবহার করলে লাইব্রেরি «আপডেট অপারেশন ও পরবর্তী প্রাপ্তির মধ্যে happens-before» নিশ্চিত করে।8
অর্থাৎ Java-এর ব্যবহারিক নির্দেশ এভাবে সারসংক্ষেপ করা যায়: কাঁচা শেয়ার করা চলক নিয়ে চালাকি করবেন না। শেয়ার করতে java.util.concurrent-এর টুল ব্যবহার করুন, happens-before লাইব্রেরিকে তৈরি করতে দিন।
৩. থ্রেড কীভাবে তৈরি করবেন — ExecutorService ও ভার্চুয়াল থ্রেড
৩.১. কাজকে চালানোর উপায় থেকে আলাদা করা
ExecutorService Java-তে «নিজে থ্রেড তৈরি করবেন না» নীতি বহন করে। কাজ (Runnable / Callable) ও তা কীভাবে চলবে — কত থ্রেড, কোন কিউ — আলাদা করে, থ্রেড তৈরি, পুনর্ব্যবহার ও ফেলা লাইব্রেরির হাতে রাখে।1
JDK 21 থেকে কীভাবে চালাবেন তা সরল দ্বৈত বাছাই হয়ে গেছে।23
flowchart TB
S["কাজ যা সমকালীন চালাতে চান"] --> Q1{"কাজকে কী চালায়?"}
Q1 -->|"প্রধানত I/O অপেক্ষা<br/>HTTP কল, DB, ফাইল"| VT["ভার্চুয়াল থ্রেড<br/>Executors.newVirtualThreadPerTaskExecutor<br/>প্রতি কাজে এক, কখনো পুল নয়"]
Q1 -->|"CPU-বাউন্ড হিসাব"| PT["প্ল্যাটফর্ম থ্রেডের স্থির পুল<br/>Executors.newFixedThreadPool - প্রায় কোর সংখ্যা<br/>বা parallel stream"]
VT --> LIMIT["বাইরের সেবায় সমকালীনতা<br/>Semaphore দিয়ে সীমাবদ্ধ করুন, পুল আকারে নয়"]
চিত্র ৩: JDK 21 থেকে কাজ চালানোর বাছাই। আগে রেখা টানুন — «I/O-এর অপেক্ষা বদলান, CPU-এর জন্য সমান্তরাল করুন» — তারপর I/O-বাউন্ড কাজ ভার্চুয়াল থ্রেডে ও CPU-বাউন্ড কাজ প্রচলিত পুলে দিন
CPU-বাউন্ড পাশে এক সতর্কতা আছে। Executors.newFixedThreadPool থ্রেড সংখ্যা সীমাবদ্ধ করে, কিন্তু তার কিউ সীমাহীন। দীর্ঘজীবী সেবায় জমা দেওয়া প্রক্রিয়াকরণ ছাড়িয়ে যেতে থাকলে কেবল থ্রেডই কোর সংখ্যায় বাঁধা — কিউতে জমা কাজ ও তাদের ডেটা মেমরি খেতে থাকে। এমন সেটআপে হয় ThreadPoolExecutor সরাসরি ব্যবহার করে সীমিত কিউ প্লাস প্রত্যাখ্যান নীতি সাজান, নয়তো জমা দেওয়ার পাশে Semaphore-এর মতো প্রবেশ নিয়ন্ত্রণ রাখুন যাতে পশ্চাৎচাপ দিতে পারেন (৪ নম্বর অংশের কিউ আলোচনার একই নীতি)।
৩.২. ভার্চুয়াল থ্রেড ভুলভাবে ব্যবহার করবেন না
ভার্চুয়াল থ্রেড OS থ্রেড থেকে আলাদা হালকা থ্রেড: JDK-এর ব্লকিং অপারেশন (মানক লাইব্রেরির I/O, লক, স্লিপ ইত্যাদি) চলাকালে তারা OS থ্রেড ছেড়ে দেয়, তাই এক JVM লক্ষ লক্ষ চালাতে পারে। তবু সব ধরনের ব্লকে ছেড়ে দেয় না। নেটিভ কোড (JNI) বা বিদেশি ফাংশন চালাতে ব্লক হলে ভার্চুয়াল থ্রেড তার ক্যারিয়ার থ্রেডে পিন থাকে। JDK 24 (JEP 491, নিচে) যা ঠিক করেছে তা synchronized-এর পিনিং; নেটিভ সীমানায় পিনিং থাকে, তাই JNI ড্রাইভার বা ডিভাইস API দিয়ে দীর্ঘ ব্লক করা কাজ বিপুল ভার্চুয়াল থ্রেডে চাপালে ক্যারিয়ার থ্রেড ফুরিয়ে যাবে। কিন্তু অফিসিয়াল গাইড জোর দেয়, তারা «দ্রুত থ্রেড» নয়। কোড চালানোর গতি বদলায় না — যা দেয় তা স্কেল (থ্রুপুট)।3
ব্যবহারের তিন শৃঙ্খলা।3
- পুল করবেন না। ভার্চুয়াল থ্রেড সস্তা ও ফেলে দেওয়ার মতো; «কাজের সংখ্যা = ভার্চুয়াল থ্রেডের সংখ্যা» সঠিক অবস্থা।
newFixedThreadPool-এ ভার্চুয়াল থ্রেড দেওয়া ভুল — রূপtry (var executor = Executors.newVirtualThreadPerTaskExecutor())ব্যবহার করুন। - সমকালীনতা
Semaphoreদিয়ে সীমাবদ্ধ করুন। «বাইরের API-তে সর্বোচ্চ ১০ সমকালীন সংযোগ»-এর মতো বাধা সেমফোর দিয়ে প্রকাশ করুন, পুল আকারে নয়। - CPU-বাউন্ড কাজে ব্যবহার করবেন না। প্রায় কোর সংখ্যার প্ল্যাটফর্ম থ্রেড হিসাব সমান্তরালের জন্য আগের মতোই সঠিক টুল।
খেয়াল রাখুন ভার্চুয়াল থ্রেডের ভেতর সাধারণ সিঙ্ক্রোনাস কোড চলে। .NET-এর async/await-এর মতো কোড আবার না লিখে, ভার্চুয়াল থ্রেডের ডিজাইন দর্শন হলো সরল «প্রতি অনুরোধে এক থ্রেড» কোড অপরিবর্তিত বিশাল স্কেলে চালাতে দেওয়া।2
৩.৩. সাধারণ ভুলবোঝা — «পুল লাগে না» কেবল ভার্চুয়াল থ্রেডের কথা
«পুল করবেন না» শৃঙ্খলা পড়ে «Java-তে থ্রেড পুল বলে কিছু নেই (বা তা অদক্ষ)» ভাবা যাবে না। বাস্তব উল্টো: Java-এর পুল JDK 5 (২০০৪) থেকে মানক লাইব্রেরির পরিণত অংশ। সূক্ষ্ম কনফিগারযোগ্য সাধারণ পুল ThreadPoolExecutor (Executors-এর বিভিন্ন ফ্যাক্টরিতে তৈরি), কাজ-চুরি ForkJoinPool (যার সাধারণ ইনস্ট্যান্স commonPool() parallel stream ও CompletableFuture-এর ডিফল্ট চালানোর লক্ষ্য), আর পর্যায়ক্রমিক চালানোর ScheduledThreadPoolExecutor — CPU-বাউন্ড কাজে এখনও এরাই মুখ্য।
পুল মূলত এই অপটিমাইজেশন: «OS থ্রেড তৈরি ও ধরে রাখা ব্যয়বহুল, তাই পুনর্ব্যবহার করুন»। ভার্চুয়াল থ্রেড তৈরির খরচ প্রায় শূন্য করে এই ভিত্তি মুছে দেয়, তাই পুনর্ব্যবহারের কারণ থাকে না — সঠিক বোঝাপড়া এই নয় যে পুল অদক্ষ হয়ে গেছে, বরং থ্রেড এত হালকা হয়েছে যে পুল অপটিমাইজেশন অপ্রয়োজনীয়। আর ভার্চুয়াল থ্রেডের তলায় JDK-এর শিডিউলার প্রায় কোর সংখ্যার ক্যারিয়ার থ্রেড (OS থ্রেড) কাজ-চুরি ForkJoinPool হিসেবে চালায়।2 অর্থাৎ «ছোট OS থ্রেড পুল দিয়ে বিপুল সমকালীন কাজ সামলানো» রূপ থাকে; কেবল সেই পুলের ব্যবস্থাপনা ডেভেলপারের হাত থেকে JVM-এ গেছে। বলা যায় Java সেই গন্তব্যে পৌঁছায় যেখানে .NET-এর async/await await-এ থ্রেড পুলে ফেরায়, কোডের রূপ না বদলে।
৪. শেয়ার করা পরিবর্তনযোগ্য অবস্থা কমানো — ভাগ, অপরিবর্তনীয়তা, সমকালীন কালেকশন ও কিউ
প্রতিযোগিতা কেবল তখন ওঠে যখন «একাধিক থ্রেড» ও «শেয়ার করা পরিবর্তনযোগ্য ডেটা» দুটোই থাকে। থ্রেডের সংখ্যা প্রয়োজন ঠিক করে, তাই ডিজাইন যা কাটতে পারে তা শেয়ার করা অংশ। তিন পরিবারের কৌশল — ভাগ করা, অপরিবর্তনীয় করা, ডেটা হস্তান্তর — আর Java-তে এভাবে লেখা হয়।
ভাগ করুন। সমান্তরাল সংকলনে প্রতি থ্রেড শেয়ার করা মোট চলকে না লিখে প্রতি থ্রেড আংশিক ফল বানাক, শেষে মেলান। Parallel stream-এর reduce / collect ঠিক এই রূপ কাঠামো হিসেবে দেয়, আর নিচে আলোচিত LongAdderও ভাগ কৌশলের বাস্তবায়ন — ভেতরে সেলে ভেঙে প্রতিযোগিতা ছড়ায়, পড়ার সময় যোগ করে। শেয়ার করা অবস্থায় কতবার লিখবেন তা কমানো সঠিক সিঙ্ক্রোনাইজেশনের আগে আসে।
অপরিবর্তনীয় করুন। record ও অপরিবর্তনীয় কালেকশন (List.copyOf / Map.copyOf) দিয়ে নির্মাণের পর আর না লেখা ডেটা বানালে সিঙ্ক্রোনাইজেশন ছাড়া শেয়ার করা যায়। কনফিগ ও মাস্টার ডেটার মানক প্যাটার্ন: বদলাতে নতুন অবজেক্ট বানিয়ে volatile রেফারেন্স বদলান। তবে «শুধু-পড়ার মতো দেখায়» আর «অপরিবর্তনীয়» আলাদা। record-এর অ্যাকসেসর তার উপাদানের কাঁচা রেফারেন্স ফেরায়, আর List.copyOf-এর কপিও অগভীর (উপাদান অবজেক্ট নকল করে না), তাই উপাদান পরিবর্তনযোগ্য হলে উপনাম ধরা যে কেউ ভেতর লিখতে পারে, প্রতিযোগিতা থাকে। সিঙ্ক্রোনাইজেশন ছাড়া শেয়ার নিরাপদ কেবল তখনই যখন পুরো অবজেক্ট গ্রাফ — উপাদানসহ — অপরিবর্তনীয়। পরিবর্তনযোগ্য উপাদান থাকলে গভীর কপি দিন, অথবা উপাদানও record / অপরিবর্তনীয় টাইপে সরান।
সমকালীন কালেকশনের যৌগিক অপারেশন ব্যবহার করুন। ConcurrentHashMap-এর «না থাকলে তৈরি করে ঢোকান» প্যাটার্নে computeIfAbsent। এই মেথড পুরো কল পরমাণুভাবে চালায়, আর কী না থাকলে ম্যাপিং ফাংশন সেই এক কলের ভেতর ঠিক একবার ডাকা হয়।8 গ্যারান্টি .NET-এর ConcurrentDictionary.GetOrAdd থেকে আলাদা (যার ফ্যাক্টরি প্রতিযোগিতায় একাধিকবার চলতে পারে) — দুই ভাষার মধ্যে যাতায়াতকারীরা সহজে গুলিয়ে ফেলেন। তবে তা «কী-এর জীবনে ঠিক একবার» নয়। ফাংশন null ফেরালে বা ছুড়লে ম্যাপিং নিবন্ধিত হয় না, পরের কলে ফাংশন আবার চলে (নিবন্ধনের পর এন্ট্রি মুছলেও একই)। আরম্ভ পুনরাবৃত্ত পার্শ্বপ্রভাব সহ্য না করলে ফাংশন সফল ও অশূন্য ফেরায় এমন করে ডিজাইন করুন। পরমাণু হওয়ার মূল্যে হিসাব চলাকালে অন্য থ্রেডের কিছু আপডেট ব্লক হয়, তাই ম্যাপিং ফাংশন ছোট রাখুন, আর ফাংশনের ভেতর এই ম্যাপ নিজে আপডেট করবেন না (ধরা পড়া পুনরাবৃত্ত আপডেট IllegalStateException ছুড়তে পারে)।8
// কম্পাঙ্ক কাউন্টারের মানক প্যাটার্ন: computeIfAbsent + LongAdder
ConcurrentHashMap<String, LongAdder> freqs = new ConcurrentHashMap<>();
freqs.computeIfAbsent(key, k -> new LongAdder()).increment();
কিউ দিয়ে ডেটা হস্তান্তর করুন। থ্রেডের মধ্যে ডেটার প্রবাহ BlockingQueue-এ পাঠান। ধারণক্ষমতা দেওয়া ArrayBlockingQueue-এ ভর্তি হলে put ব্লক করে স্বাভাবিক পশ্চাৎচাপ দেয় — .NET সংস্করণের bounded চ্যানেলের একই রূপ। ভার্চুয়াল-থ্রেড যুগেও উৎপাদক ও ভোক্তার মধ্যে স্পষ্ট সীমা টানার এই ডিজাইন কার্যকর।
৫. লকিংয়ের শৃঙ্খলা — synchronized ও ReentrantLock
৫.১. কীসে লক করবেন, লক ধরে কী করবেন না
লকিংয়ের একক «কোডের অংশ» নয়, «ডেটা» ভেবে নিন। রক্ষা করতে চান এমন প্রতিটি পরিবর্তনযোগ্য ডেটাসেটে এক লক অবজেক্ট ম্যাপ করুন, আর সেই ডেটা স্পর্শ করে সব জায়গায় সেই একই লক নিন — রেস বাগের বাস্তব সাধারণত এই ম্যাপিং কোথাও ভেঙেছে। synchronized(this) ও synchronized(SomeClass.class) এড়িয়ে চলুন, কারণ বাইরের কোড একই অবজেক্ট লক করতে পারে; তার বদলে রক্ষা করতে চাওয়া ডেটাকে এক-এক করে private final Object lock = new Object(); যা বাইরে কখনো প্রকাশিত হয় না-এর সঙ্গে জুড়ুন।
তার ওপরে আর দুই শৃঙ্খলা। প্রথম, লক ধরে ধীর কিছু করবেন না, বাইরের জগতে ছোঁয়া কিছুও নয়। লক ধরেই I/O, লিসনার ডাকা, বা অজানা কোড চালানো ধরে রাখার সময় বাড়ায় আর ঝুঁকি যে ডাকা পক্ষ অন্য লক নিতে চাইবে, চিত্র ২-এর বৃত্তাকার অপেক্ষা তৈরি করবে। দ্বিতীয়, একাধিক লকের অধিগ্রহণ ক্রম স্থির করুন। দুই বা তার বেশি লক নেওয়া জায়গায় নিয়ম করুন সব থ্রেড একই ক্রমে নেবে, আর ক্রম নিশ্চিত করা যায় না এমন জায়গায় নিচে আলোচিত tryLock(timeout) দিয়ে «না পেলে ছেড়ে আবার চেষ্টা» পথ রাখুন।
synchronized «ছোট, সরল বর্জন»-এর জন্য যথেষ্ট। নিচেরগুলো লাগলে ReentrantLock-এ যান।
tryLock(timeout)দিয়ে সময়সীমাসহ অধিগ্রহণ (চিরস্থায়ী হ্যাংকে লগ ও সামলানো যায় এমন ব্যর্থতায় বদলানো)- ন্যায্যতা নীতি, একাধিক
Condition, বা লক নেওয়া ও ছাড়া আলাদা মেথডে ভাগ করতে চাইলে
ReentrantLock ব্যবহারকালে lock()-এর ঠিক পরে try আর finally-তে unlock() প্যাটার্ন ভাঙবেন না (Java-তে C++-এর RAII-এর সমতুল্য নেই, তাই এই প্যাটার্নই পুরো শৃঙ্খলা)।
৫.২. ভার্চুয়াল থ্রেড ও পিনিং — JDK 24-এ কী বদলেছে
ভার্চুয়াল থ্রেড প্রথম আসার সময় (JDK 21-23) বাধা ছিল যে synchronized ব্লকের ভেতর ব্লক করলে ভার্চুয়াল থ্রেড তার OS থ্রেডে পিন হয় (OS থ্রেড ছাড়তে পারে না, স্কেলের সুবিধা হারায়), আর ঘন বা দীর্ঘ ব্লক স্থান ReentrantLock দিয়ে বদলানো সুপারিশ ছিল।3 সেই বাধা JDK 24-এর JEP 491 মনিটর বাস্তবায়ন নতুন করে লিখে সরিয়েছে, আর synchronized আর ভার্চুয়াল থ্রেড পিন করে না।4 JDK 24 বা পরে থাকলে পিনিংয়ের প্রতিরোধ হিসেবে যান্ত্রিকভাবে synchronized বদলানো আর দরকার নেই। সংস্থার পুরনো নির্দেশিকা এখনও JDK 21-যুগের সতর্কতায় আটকে আছে কিনা দেখা মূল্যবান।
৫.৩. পরমাণু ও volatile কোথায় বসে
এক চলকের পরমাণু আপডেট AtomicInteger / AtomicLong / AtomicReference সামলায় (বা কেবল উচ্চ কম্পাঙ্কে বাড়ানো পরিসংখ্যানে প্রতিযোগিতাসহনশীল LongAdder)। volatile দৃশ্যমানতা ও ক্রম (happens-before) নিশ্চিত করে, যৌগিক অপারেশনের পরমাণুত্ব নয়।5 .NET ও C++ সংস্করণের একই সিদ্ধান্ত Java-তেও দাঁড়ায়: পতাকা ও একক মানে পরমাণু, যৌগিক অবস্থায় লক, volatile-কে একা দিয়ে করানোর চেষ্টা করবেন না।
৬. থামানোর ডিজাইন — interruption সাধারণ ভাষা হিসেবে
৬.১. interrupt-এর শিষ্টাচার
Java-তে থামানো ও বাতিল interruption-কে কেন্দ্র করে একীভূত। t.interrupt() লক্ষ্য থ্রেডের interrupt অবস্থা সেট করে, আর লক্ষ্য sleep / wait / join ইত্যাদিতে ব্লক থাকলে InterruptedException ছুড়ে তৎক্ষণাৎ জাগায় (তখন interrupt অবস্থা মুছে যায়)।6 Thread.stop / suspend / resume, অতীতের জবরদস্তি ব্যবস্থা, মৌলিকভাবে অনিরাপদ, তাই এখন ডাকলে UnsupportedOperationException হয়।6
flowchart TB
OWNER["কলার থামায় - t.interrupt ডাকে"] --> ST["Interrupt অবস্থা সেট হয়"]
ST --> A["হিসাব করা থ্রেড:<br/>লুপে Thread.interrupted দেখে"]
ST --> B["sleep / wait / join-এ ব্লক:<br/>InterruptedException চলে তৎক্ষণাৎ জাগায়<br/>অবস্থা মুছে যায়"]
A --> E["পরিষ্কার করে নিজে শেষ করে"]
B --> C{"catch কী করে?"}
C -->|"নিজে শেষ করতে পারে"| E
C -->|"শেষ করতে পারে না - যেমন লাইব্রেরির ভেতর"| R["Thread.currentThread.interrupt<br/>অবস্থা ফিরিয়ে সংকেত রাখে"]
R --> E
চিত্র ৪: Interruption দিয়ে সহযোগী থামানো। InterruptedException গিললে থামার সংকেত উধাও — ধরার পর বাছাই «শেষ» বা «ফেরান»
অনুশীলনে মনে রাখার শৃঙ্খলা একটিই: এমন কোড লিখবেন না যা InterruptedException ধরে কিছু করে না। নিজ দায়িত্বে শেষ করতে পারলে সেখানেই শেষ; না পারলে Thread.currentThread().interrupt() দিয়ে অবস্থা ফিরিয়ে কলারকে সংকেত দিন (FAQ দেখুন)।
৬.২. ExecutorService-এর দুই-ধাপ শাটডাউন
ExecutorService-এর শাটডাউন API interruption মডেলের ওপর বসে। shutdown() নতুন কাজ নেওয়া বন্ধ করে আগে জমা কাজ সম্পূর্ণ হতে দেয়; shutdownNow() চলন্ত কাজ থামানোর চেষ্টা করে। ইন্টারফেস স্পেসিফিকেশন হিসেবে এটি best-effort, আর স্পষ্ট নথিভুক্ত যে মানক বাস্তবায়ন (যেমন ThreadPoolExecutor) সাধারণত Thread.interrupt() দিয়ে বাতিল করে — অর্থাৎ interruption-এ সাড়া দেয় না এমন কাজ shutdownNow দিয়েও থামবে না, আর কাস্টম Executor বাস্তবায়ন ব্যবহার করলে তার নথিতে দেখুন কীভাবে বাতিল করে (interrupt পাঠায় কি না)।1 অফিসিয়াল নথি দেখানো মানক থামানোর প্যাটার্ন নিচের দুই-ধাপ প্যাটার্ন।1
/** শাটডাউন সম্পূর্ণ হলে সত্য। এটি মিথ্যা থাকতে শেয়ার করা সম্পদ মুক্ত করতে এগোবেন না। */
boolean shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // ধাপ 1: নতুন কাজ নেওয়া বন্ধ ও সম্পূর্ণতার অপেক্ষা
try {
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
// ধাপ 2: বাতিল চাইুন। চালানোর আগে নামানো কাজ
// ফেরে, তাই তাদের Future বাতিল চিহ্নিত করুন যাতে get()-এ অপেক্ষমাণ কলার জাগে
pool.shutdownNow().forEach(r -> {
if (r instanceof Future<?> f) f.cancel(false);
});
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
System.err.println("Pool did not terminate");
return false; // শাটডাউন অসম্পূর্ণ। সাফল্য থেকে আলাদা করে জানান
}
}
return true;
} catch (InterruptedException ex) {
pool.shutdownNow().forEach(r -> {
if (r instanceof Future<?> f) f.cancel(false);
});
Thread.currentThread().interrupt(); // এই থ্রেডের interrupt অবস্থাও ফেরান
return false; // এই পথও শাটডাউন অসম্পূর্ণ রাখতে পারে
}
}
flowchart TB
S["shutdown<br/>নতুন কাজ নেওয়া বন্ধ"] --> W1{"awaitTermination<br/>সম্পূর্ণতার অপেক্ষা"}
W1 -->|"সময়সীমায় শেষ"| DONE["শাটডাউন সম্পূর্ণ"]
W1 -->|"সময় ফুরিয়েছে"| NOW["shutdownNow<br/>চলন্ত কাজে interrupt পাঠায়<br/>সাড়া দেবে কিনা কাজের ওপর"]
NOW --> W2{"awaitTermination<br/>আবার অপেক্ষা"}
W2 -->|"সম্পূর্ণ"| DONE
W2 -->|"এখনও শেষ নয়"| LOG["অস্বাভাবিক হিসেবে লগ<br/>interrupt উপেক্ষা করা কাজ সন্দেহ"]
চিত্র ৫: ExecutorService-এর দুই-ধাপ শাটডাউন। ধাপভিত্তিক ডিজাইন «ভদ্রভাবে অপেক্ষা → interruption দিয়ে চাওয়া → তবু না শেষ হলে অস্বাভাবিক হিসেবে দেখা»
shutdownNow() যে Runnable ফেরায় তার বাতিলের এক সীমা আছে। যা ফেরে তা চালানোর কিউতে বসা অবজেক্ট — সাধারণ submit-এ তা কলারকে দেওয়া FutureTask নিজে, কিন্তু ExecutorCompletionService-এর মতো র্যাপার দিয়ে জমা কাজের ক্ষেত্রে তা কিউয়ের ভেতরের র্যাপার, কলারের Future থেকে আলাদা অবজেক্ট। সেই গঠনে উপরের বাতিল কলারের Future সম্পূর্ণ করবে না, তাই জমা দেওয়ার সময় নিজের Future তালিকা রেখে শাটডাউনে সেগুলো বাতিল করুন (বা নামানো কাজ মালিককে ফেরান)।
close() (AutoCloseable), JDK 19 থেকে পাওয়া, «shutdown ডেকে সম্পূর্ণতা পর্যন্ত অপেক্ষা» try-with-resources-এ লেখার রূপে প্যাক করে, আর ভার্চুয়াল-থ্রেড newVirtualThreadPerTaskExecutor-এর সঙ্গে try (var executor = ...) আধুনিক মৌলিক রূপ।1 তবে close() উপরের দুই-ধাপ প্যাটার্নের বিকল্প নয়। কারণ এটি সময়সীমা ছাড়া সম্পূর্ণতার অপেক্ষা করে, একটা কাজও interruption-এ সাড়া না দিলে বা কখনো না শেষ হলে বন্ধ করতে চাওয়া থ্রেড চিরকাল ব্লক হয়। কাজ সসীম ও সম্পূর্ণতা নিশ্চিত এমন পরিসরে উপযুক্ত টুল (সেখানেই জমা দিন, সেখানেই অপেক্ষা); অ্যাপের শাটডাউন পথের মতো জায়গায়, যেখানে সবসময় সীমিত সময়ে শেষ চাই, সময়সীমাসহ দুই-ধাপ প্যাটার্ন ব্যবহার করুন। একক কাজ বাতিলও interruption দিয়ে হয়, Future.cancel(true) দিয়ে।
৭. UI থ্রেড — Swing-এর EDT
ডেস্কটপ অ্যাপ, ভাষা বা ফ্রেমওয়ার্ক যাই হোক, নিয়ম মেনে চলে যে UI সেই থ্রেডের একান্ত যা তাকে পরিচালনা করে। Swing-এ সেই একান্ত থ্রেড Event Dispatch Thread (EDT): Swing কম্পোনেন্ট মেথড নিয়মত থ্রেড-নিরাপদ নয়, একাধিক থ্রেড থেকে ছোঁয়া থ্রেড হস্তক্ষেপ ও মেমরি সামঞ্জস্য ত্রুটি ডাকে। অন্য থ্রেড থেকে স্ক্রিন আপডেট SwingUtilities.invokeLater দিয়ে EDT-কে চান, আর উল্টো, EDT-তে দীর্ঘ অপারেশন UI জমাট বাঁধায় বলে ভারী কাজ SwingWorker ইত্যাদি দিয়ে ওয়ার্কার থ্রেডে পাঠান।7 JavaFX একই রূপ: UI আপডেট Platform.runLater দিয়ে অ্যাপ্লিকেশন থ্রেডে চাওয়া হয়।
৮. যাচাই ও ডিবাগ — থ্রেড ডাম্প অস্ত্র হিসেবে
পরীক্ষা রেস বাগ খুঁজে পাবে আশা করা যায় না। সাধারণ পরীক্ষা প্রতিযোগিতা সৌভাগ্যক্রমে না ঘটা চালানকে সাফল্য গণে। প্রতিরক্ষা তিন স্তরে ভাবুন।
প্রথম প্রতিরক্ষা রেখা ডিজাইন। রিভিউতে সারণি দিয়ে দেখুন: কোন পরিবর্তনযোগ্য ডেটা শেয়ার করা, প্রতিটি আইটেম কোন লক রক্ষা করে (৫.১-এর ম্যাপিং), লক অধিগ্রহণ ক্রম কি অনন্য, কোন catch ব্লক InterruptedException গিলছে কি না, আর থামার পথ (shutdown/interruption) সব কাজে পৌঁছায় কি না।
দ্বিতীয়, থ্রেড ডাম্প ভালো করে ব্যবহার করুন। Java-এর «এই জমে যাওয়া মুহূর্তে সব থ্রেডের অবস্থা» ধরার মানক টুল আছে: jstack (বা jcmd <pid> Thread.print) স্ট্যাক ট্রেস ছাপায়, -l অপশন লক তথ্যও যোগ করে।9 খেয়াল রাখুন এই প্রথাগত ডাম্প ফরম্যাট প্ল্যাটফর্ম থ্রেডের জন্য; অ্যাপের ভার্চুয়াল থ্রেড অন্তর্ভুক্ত নয়। ভার্চুয়াল থ্রেড ব্যবহার করা গঠনে (৩ নম্বর অংশ) ব্লক অনুরোধ ট्रेस করতে jcmd <pid> Thread.dump_to_file -format=json <file> ব্যবহার করুন, যা ভার্চুয়াল থ্রেডও ডাম্প করতে পারে।2 হ্যাং তদন্তের মৌলিক পদ্ধতি কয়েক সেকেন্ড অন্তর দুই-তিন ডাম্প নিয়ে মিলিয়ে দেখা কোন নিষ্ক্রিয় থ্রেড কোন লকের অপেক্ষা করছে, আর সেই লক কে ধরে আছে। tryLock(timeout)-এর সময় শেষ লগ রাখলে (৫.২) ডাম্প নেওয়ার ট্রিগার স্বয়ংক্রিয়ও করা যায়।
তৃতীয়, লোডে নাড়ুন। স্ট্রেস টেস্ট — কোরের চেয়ে বেশি সমান্তরালে দীর্ঘ চালানো, প্রক্রিয়াকরণ ক্রম এলোমেলো করা, কৃত্রিম বিলম্ব ঢোকানো — ডেভেলপমেন্ট মেশিনে রেস কন্ডিশনের «হিট» টানার সম্ভাবনা বাড়ানোর বাস্তব উপায়। রিলিজের আগে অন্তত একবার প্রোডাকশন-স্কেল ডেটা ভলিউম ও থ্রেড সংখ্যায় পরীক্ষা চালান।
৯. Java সমকালীনতা কোথায় যাচ্ছে — Structured Concurrency
আধা পা এগিয়ে এক ঝলক, শেষ করতে। ভার্চুয়াল থ্রেডের অনুমানে তৈরি Structured Concurrency (StructuredTaskScope) — যা একাধিক উপকাজকে এক কাজের একক ধরে ব্যর্থতা প্রসারণ ও বাতিল গঠন করে — উন্নয়নাধীন, আর আগস্ট ২০২৬ পর্যন্ত এখনও প্রিভিউ ফিচার। JDK 25-এর পঞ্চম প্রিভিউতে (JEP 505) StructuredTaskScope.open() ভিত্তিক API রূপে সংশোধিত, আর বর্তমান JDK 26-এ ষষ্ঠ প্রিভিউ (JEP 525) হিসেবে চলছে।1011 এদিকে Scoped Values, অপরিবর্তনীয় প্রসঙ্গ শেয়ার যা ThreadLocal-এর সমস্যা সমাধান করে, JDK 25-এ চূড়ান্ত হয়েছে।12 এই নিবন্ধের নীতি — স্পষ্ট কাজের সীমা, অপরিবর্তনীয় শেয়ার, সহযোগী থামানো — এই নতুন API-এর দিকের সঙ্গেও মিলে।
১০. সারসংক্ষেপ — Java চেকলিস্ট
- ব্যবসায়িক কোডে কোনো
new Threadবাকি আছে কি (ExecutorService/ ভার্চুয়াল থ্রেডে চড়েছে)? - I/O-বাউন্ড ও CPU-বাউন্ড কাজ আলাদা চালানোর ব্যবস্থায় পাঠানো হয়েছে (চিত্র ৩-এর শাখা)?
- ভার্চুয়াল থ্রেড পুল হচ্ছে না, সমকালীনতা
Semaphoreদিয়ে সীমাবদ্ধ? - শেয়ার করা ডেটা অপরিবর্তনীয় (
record/List.copyOf), নাকিjava.util.concurrent-এর টুলে চড়েছে? synchronized(this)বা প্রকাশ্য অবজেক্টে লক নেই?ConcurrentHashMap-এর যৌগিক অপারেশন (computeIfAbsentইত্যাদি) ব্যবহার করে ম্যাপিং ফাংশন ছোট রাখা হয়েছে?volatileথেকে পরমাণুত্ব আশা করা হচ্ছে না (কাউন্টার Atomic ক্লাস /LongAdderব্যবহার করছে)?- একটাও catch ব্লক
InterruptedExceptionগিলছে না? ExecutorServiceথামানো সময়সীমাসহ দুই-ধাপ প্যাটার্ন মেনে চলে (close()ব্যবহারের জায়গা কাজের সম্পূর্ণতা নিশ্চিত পরিসরে সীমাবদ্ধ)?- Swing/JavaFX UI আপডেট EDT / অ্যাপ্লিকেশন থ্রেডে একত্রিত?
Java সমকালীনতার টুলে সবচেয়ে সজ্জিত ভাষাগুলোর একটা, আর ভার্চুয়াল থ্রেডের আগমন সরল সিঙ্ক্রোনাস কোড আবার না লিখে স্কেল করার পথ খুলেছে। তাই টুলের শ্রমভাগ ঠিক ধরা — কোনটা থ্রুপুটের, কোনটা বর্জনের, থামার সংকেত কী — Java-তে মাল্টিথ্রেডিং ডিজাইনের সার।
সম্পর্কিত নিবন্ধ
- ব্যবহারিক মাল্টিথ্রেডিং বেস্ট প্র্যাকটিস: .NET সংস্করণ — আর থ্রেড যোগ করার আগে কী ঠিক করবেন
- ব্যবহারিক মাল্টিথ্রেডিং বেস্ট প্র্যাকটিস: C++ সংস্করণ — RAII ও jthread দিয়ে কাঠামোয় দুর্ঘটনা সরানো
- ব্যবহারিক মাল্টিথ্রেডিং বেস্ট প্র্যাকটিস: C সংস্করণ — Win32 API পথে নিরাপদে লেখা
- A Practical Decision Table for C# async/await - Task.Run and ConfigureAwait
সম্পর্কিত পরামর্শ ক্ষেত্র
KomuraSoft LLC Java ব্যবসায়িক সিস্টেম ও ব্যাচ প্রক্রিয়াকরণের মাল্টিথ্রেডিং ডিজাইন রিভিউ, শেয়ার করা অবস্থা নষ্ট হওয়া ও «মাঝে মাঝে থামে না / হ্যাং» জাতীয় সমকালীনতা-সংক্রান্ত ত্রুটির মূল কারণ তদন্ত (থ্রেড ডাম্প বিশ্লেষণ), আর ভার্চুয়াল থ্রেড গ্রহণের প্রযুক্তিগত পরামর্শ সামলায়।
তথ্যসূত্র
-
Oracle, ExecutorService (Java SE 21 & JDK 21 API)। shutdown() আগে জমা কাজ সম্পূর্ণ হতে দিয়ে নতুন জমা বন্ধ করে; shutdownNow() চলন্ত কাজ থামানোর চেষ্টা করে ও অপেক্ষমাণ কাজের তালিকা ফেরায়, যদিও সাধারণ বাস্তবায়ন Thread.interrupt() দিয়ে বাতিল করে, best-effort ছাড়িয়ে গ্যারান্টি নেই, তাই interruption-এ সাড়া না দেওয়া কাজ শেষ হয় না; awaitTermination দিয়ে সম্পূর্ণতার অপেক্ষা করা যায়; close() (AutoCloseable, Java 19 থেকে) shutdown ডেকে সম্পূর্ণতার অপেক্ষা করে, try-with-resources-এ ব্যবহারযোগ্য; আর shutdown → awaitTermination → shutdownNow দুই-ধাপ শাটডাউন ব্যবহার উদাহরণ হিসেবে দেখানো সম্পর্কে। ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
OpenJDK, JEP 444: Virtual Threads। ভার্চুয়াল থ্রেড JDK 21-এ সরকারি ফিচার হওয়া; উচ্চ-থ্রুপুট সমকালীন অ্যাপ লেখা, রক্ষা ও পর্যবেক্ষণের পরিশ্রম নাটকীয়ভাবে কমানো হালকা থ্রেড হওয়া; সরল «এক অনুরোধ, এক থ্রেড» সিঙ্ক্রোনাস কোড অপরিবর্তিত স্কেল করার ডিজাইন দর্শন; JDK-এর ভার্চুয়াল থ্রেড শিডিউলার FIFO মোডে কাজ-চুরি ForkJoinPool, ডিফল্ট সমান্তরালতা উপলব্ধ প্রসেসর সংখ্যার সমান; আর ভার্চুয়াল থ্রেডসহ নতুন থ্রেড ডাম্প ফরম্যাট jcmd Thread.dump_to_file (সাধারণ পাঠ্য ও JSON) হিসেবে যোগ হওয়া, প্রথাগত থ্রেড ডাম্পে ভার্চুয়াল থ্রেড না থাকা সম্পর্কে। ↩ ↩2 ↩3 ↩4 ↩5
-
Oracle Java SE Core Libraries, Virtual Threads। ভার্চুয়াল থ্রেড Java রানটাইম বাস্তবায়িত হালকা থ্রেড যা ব্লকিং I/O-তে OS থ্রেড ছাড়ে; সেগুলো গতি (লেটেন্সি) নয় স্কেল (থ্রুপুট) ফিচার, CPU-নিবিড় প্রক্রিয়াকরণে অনুপযুক্ত; ভার্চুয়াল থ্রেড কখনো পুল না করে প্রতি কাজে এক ব্যবহার (newVirtualThreadPerTaskExecutor); সমকালীনতা সীমাবদ্ধ করতে থ্রেড পুল নয় Semaphore; JDK 21 পর্যন্ত synchronized-এর ভেতর ব্লক OS থ্রেডে পিনিং ঘটায়, তাই ঘন বা দীর্ঘ স্থান ReentrantLock দিয়ে বদলানোর পরামর্শ; আর -Djdk.tracePinnedThreads দিয়ে পিনিং ধরা যায় সম্পর্কে। ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
OpenJDK, JEP 491: Synchronize Virtual Threads without Pinning। JDK 24-এ JVM-এর মনিটর বাস্তবায়ন ভার্চুয়াল থ্রেড সমর্থনে নতুন করে লেখা, যাতে synchronized ব্লক বা মেথডের ভেতর ব্লক আর ভার্চুয়াল থ্রেডকে ক্যারিয়ার থ্রেডে পিন করে না; আর এর অর্থ JDK 21-23 যুগের «synchronized-কে ReentrantLock দিয়ে বদলান» প্রতিরোধ নীতিতে আর দরকার নেই সম্পর্কে। ↩ ↩2
-
Oracle, The Java Tutorials, Memory Consistency Errors। মেমরি সামঞ্জস্য ত্রুটি তখন ওঠে যখন একাধিক থ্রেডের একই ডেটার অসংগত দৃশ্য থাকে; এড়ানোর চাবিকাঠি happens-before সম্পর্ক (এক স্টেটমেন্টের মেমরি লেখা অন্য স্টেটমেন্টে দেখা যায় এমন গ্যারান্টি); আর synchronized, volatile ও Thread.start / join প্রভৃতি happens-before তৈরি করে সম্পর্কে। ↩ ↩2 ↩3
-
Oracle, Thread (Java SE 21 & JDK 21 API)। Thread.stop / suspend / resume মৌলিকভাবে অনিরাপদ (লক অসংগত অবস্থায় মুক্ত হয়, ভাঙা অবজেক্ট দেখা যায়; suspend ডেডলক ডাকতে পারে), তাই অপসারণের জন্য অবচয়িত, এখন ডাকলে UnsupportedOperationException ছোড়ে; interrupt() interrupt অবস্থা সেট করে ও sleep / wait / join-এ ব্লক থ্রেডকে InterruptedException ছুড়ে জাগায় (যা interrupt অবস্থা মুছে); আর interrupted() ও isInterrupted() সেই অবস্থা কীভাবে ব্যবহার করে তার পার্থক্য সম্পর্কে। ↩ ↩2 ↩3
-
Oracle, The Java Tutorials, The Event Dispatch Thread। Swing-এর ইভেন্ট-হ্যান্ডলিং কোড Event Dispatch Thread (EDT)-এ চলে; অধিকাংশ Swing অবজেক্ট মেথড থ্রেড-নিরাপদ নয়, একাধিক থ্রেড থেকে ডাক থ্রেড হস্তক্ষেপ ও মেমরি সামঞ্জস্য ত্রুটি ডাকে, অর্থাৎ Swing কম্পোনেন্টে প্রবেশ নিয়মত EDT-তে হওয়া উচিত; অন্য থ্রেড থেকে SwingUtilities.invokeLater / invokeAndWait দিয়ে EDT-তে কাজ চাওয়া; আর EDT-তে চলা কাজ দ্রুত শেষ হওয়া উচিত সম্পর্কে। ↩ ↩2
-
Oracle, ConcurrentHashMap (Java SE 21 & JDK 21 API)। computeIfAbsent-এর পুরো মেথড কল পরমাণুভাবে চলে, কী না থাকলে ম্যাপিং ফাংশন ঠিক একবার ডাকা হয়; হিসাব চলাকালে অন্য থ্রেডের কিছু আপডেট অপারেশন ব্লক হয়, তাই ছোট ও সরল রাখা উচিত; ম্যাপিং ফাংশন এই ম্যাপ নিজে বদলাতে পারবে না, ধরা পড়া পুনরাবৃত্ত আপডেট IllegalStateException হয়; আর প্রাপ্তি অপারেশন (get) ব্লক করে না, নির্দিষ্ট কী-এর আপডেট ও পরবর্তী প্রাপ্তির মধ্যে happens-before সম্পর্ক থাকে সম্পর্কে। ↩ ↩2 ↩3
-
Oracle, The jstack Command (Java SE 21 Tools Reference)। jstack নির্দিষ্ট Java প্রক্রিয়ার সব থ্রেডের স্ট্যাক ট্রেস (ক্লাস নাম, মেথড নাম, লাইন নম্বর) ছাপায়; -l অপশন অতিরিক্ত লক তথ্যসহ বিস্তারিত প্রদর্শন চালু করে; আর jcmd-এর মতো অন্য নির্ণয় টুলের সঙ্গে ব্যবহার হয় সম্পর্কে। ↩
-
OpenJDK, JEP 505: Structured Concurrency (Fifth Preview)। Structured concurrency API সম্পর্কিত উপকাজের দলকে এক কাজের একক ধরে ত্রুটি প্রসারণ ও বাতিল গঠন করে; StructuredTaskScope স্থির ফ্যাক্টরি মেথড (open) দিয়ে খোলার রূপে সংশোধিত; আর JDK 25 পর্যন্ত পঞ্চম প্রিভিউ, এখনও চূড়ান্ত ফিচার নয় সম্পর্কে। ↩
-
OpenJDK, JEP 525: Structured Concurrency (Sixth Preview)। Structured concurrency JDK 26-এও ষষ্ঠ প্রিভিউ হিসেবে চলছে — অর্থাৎ আগস্ট ২০২৬ পর্যন্ত বর্তমান JDK-তে এখনও প্রিভিউ ফিচার, ব্যবহার করতে প্রিভিউ ফিচার চালু করতে হয় সম্পর্কে। ↩
-
OpenJDK, JEP 506: Scoped Values। Scoped Values JDK 25-এ চূড়ান্ত হয়েছে; আর সেগুলো থ্রেডের ভেতর ও জুড়ে অপরিবর্তনীয় প্রসঙ্গ ডেটা নিরাপদ ও দক্ষভাবে শেয়ার করার ব্যবস্থা, ThreadLocal-এর সমস্যার (পরিবর্তনযোগ্যতা, জীবনচক্র ব্যবস্থাপনা, উত্তরাধিকার খরচ) সমাধান দেয় সম্পর্কে। ↩
সম্পর্কিত নিবন্ধ
কাছাকাছি বিষয়ে গভীরে যেতে একই ট্যাগযুক্ত সাম্প্রতিক নিবন্ধ।
ব্যবহারিক মাল্টিথ্রেডিং সেরা অনুশীলন: C সংস্করণ — Win32 API পথে নিরাপদে লেখা
Win32-এর সাথে C-তে মাল্টিথ্রেডিংয়ের স্থিত পথ হল _beginthreadex দিয়ে থ্রেড তৈরি, SRW লক ও কন্ডিশন ভেরিয়েবল, Interlocked ফাংশন, এবং স্টপ...
ব্যবহারিক মাল্টিথ্রেডিং সেরা অনুশীলন: C++ সংস্করণ — RAII ও jthread দিয়ে কাঠামো থেকে দুর্ঘটনা মুছে ফেলা
C++-এ মাল্টিথ্রেডিং এমন জগৎ যেখানে ডেটা রেসই অনির্ধারিত আচরণ। এই নিবন্ধ std::thread-এর ডেস্ট্রাক্টরের ফাঁদ, jthread ও stop_token দিয়ে থা...
Win32 থ্রেড পুল API — CreateThreadpoolWork দিয়ে থ্রেড তৈরি না করে কনকারেন্সি
নেটিভ কোডে চারদিকে CreateThread ডাকছেন? এই নিবন্ধ Vista-তে নতুন করে সাজানো Win32 থ্রেড পুল API ব্যাখ্যা করে — work, timer, wait ও io চারট...
DllMain ও লোডার লক — DLL ইনিশিয়ালাইজেশনে "কিছুই করবেন না" বলা হয় তার আসল কারণ
DllMain থেকে LoadLibrary ডাকা বা অন্য থ্রেডের সাথে সিঙ্ক্রোনাইজ করা যায় না কেন। প্রাথমিক উৎস থেকে এই নিবন্ধ ব্যাখ্যা করে লোডার লক প্রতিট...
"Not Responding" আসলে কী — Windows কীভাবে অ্যাপ হ্যাং ধরে, আর যে ডিজাইন হ্যাং করে না
Windows-এর "Not Responding" এমন যন্ত্র যেখানে OS বিচার করে উইন্ডো ৫ সেকেন্ড মেসেজ তোলেনি আর তাকে ঘোস্ট উইন্ডো দিয়ে বদলায়। এই নিবন্ধ সেই...
সম্পর্কিত বিষয়
এই পৃষ্ঠাগুলো বিষয়টিকে সেবা ও সিদ্ধান্তের বৃহত্তর প্রেক্ষাপটে স্থাপন করে।
Windows-এর প্রযুক্তিগত বিষয়
Windows ডেভেলপমেন্ট, বাগ তদন্ত ও বিদ্যমান সম্পদ ব্যবহারের প্রবেশদ্বার।
প্রায়শ জিজ্ঞাসিত প্রশ্ন
এই নিবন্ধের বিষয়ে পরামর্শে প্রায়ই আসা প্রশ্ন।
- এখন ভার্চুয়াল থ্রেড থাকলে আর থ্রেড পুল (ExecutorService) লাগে না?
- ব্যবহারের ওপর নির্ভর করে। ভার্চুয়াল থ্রেড হলো I/O-অপেক্ষাপ্রধান কাজ বিপুল সংখ্যায় চালানোর ব্যবস্থা; তারা কোড দ্রুত করে না, থ্রুপুট বাড়ায়। I/O-বাউন্ড কাজের জন্য প্রতি কাজে এক ভার্চুয়াল থ্রেড ব্যবহার করুন (Executors.newVirtualThreadPerTaskExecutor) এবং ভার্চুয়াল থ্রেড কখনো পুল করবেন না। অন্যদিকে, CPU পুরো খরচ করা হিসাব সমান্তরাল করতে আগের মতোই কোর সংখ্যায় সীমিত প্ল্যাটফর্ম থ্রেডের পুল (বা parallel stream) সঠিক সরঞ্জাম। আর বাইরের সেবায় সমকালীন সংযোগের সংখ্যা সীমাতে চাইলে ভার্চুয়াল-থ্রেড যুগের সুপারিশ হলো পুল আকারে নয়, Semaphore দিয়ে সীমাবদ্ধ করা।
- synchronized ব্যবহার করব নাকি ReentrantLock?
- ছোট, সরল বর্জনের জন্য synchronizedই যথেষ্ট, কোডও সংক্ষিপ্ত থাকে। tryLock দিয়ে সময়সীমাসহ অধিগ্রহণ, ন্যায্যতা নীতি, বা একাধিক Condition লাগলে ReentrantLock বেছে নিন। ভার্চুয়াল থ্রেডের সঙ্গে মেশানোয় ঐতিহাসিক সতর্কতা আছে: JDK 21-23-এ সমস্যা ছিল যে synchronized ব্লকের ভেতর ব্লক করলে ভার্চুয়াল থ্রেড তার OS থ্রেডে পিন হত, তাই ঘন বা দীর্ঘ ব্লক স্থান ReentrantLock দিয়ে বদলানো সুপারিশ ছিল। JDK 24 (JEP 491) মনিটর বাস্তবায়ন নতুন করে লিখে এই বাধা সরিয়েছে। JDK 24 থেকে পিনিংয়ের কারণে synchronized বদলানোর দরকার নেই।
- volatile যোগ করলে কি কিছু থ্রেড-নিরাপদ হয়?
- না। Java-এর volatile সেই চলকে লেখা ও পড়ার মধ্যে happens-before সম্পর্ক তৈরি করে, দৃশ্যমানতা (সর্বশেষ লেখা অন্য থ্রেড দেখে) ও ক্রম নিশ্চিত করে, কিন্তু «পড়ুন, হিসাব করুন, ফিরিয়ে লিখুন»-এর মতো যৌগিক অপারেশনের পরমাণুত্ব নিশ্চিত করে না। একাধিক থ্রেড থেকে volatile int কাউন্টারে ++ করলে যোগ হারিয়ে যায়। কাউন্টারে AtomicInteger / AtomicLong (উচ্চ-কম্পাঙ্ক সংকলনে LongAdder) ব্যবহার করুন, আর কয়েকটি চলক একসঙ্গে রক্ষা করলে লক। volatile প্রায় কেবল সরল অবস্থা-পতাকা জাতীয় পরিস্থিতিতে উপযুক্ত — যেখানে এক থ্রেড লেখে আর বাকিরা শুধু পড়ে।
- InterruptedException ধরে উপেক্ষা করা কি ঠিক?
- না। Interruption Java-এর থামানো ও বাতিলের মানক সংকেত, আর গিলে ফেললে এমন থ্রেড তৈরি হয় যা থামবে না। InterruptedException ছোড়ার মুহূর্তে interrupt অবস্থা ইতিমধ্যে মুছে যায়, তাই নিজে কাজ শেষ করতে না পারলে Thread.currentThread().interrupt() দিয়ে অবস্থা ফিরিয়ে কলারের জন্য সংকেত রাখুন, অথবা ব্যতিক্রম যেমন আছে তেমনই উপরে ছুড়ুন। খালি catch ব্লক যা কিছু করে না সেগুলো শাটডাউন কাজ করে না বা shutdownNow উপেক্ষিত হয় এমন বাগের ক্লাসিক কারণ।
- Thread.stop দিয়ে থ্রেড থামানো যায় না?
- না, যায় না। Thread.stop মৌলিকভাবে অনিরাপদ (লক অসংগত অবস্থায় রেখে মুক্ত করে, ভাঙা অবজেক্ট অন্য থ্রেডে দেখায়), তাই দীর্ঘদিন অবচয়িত, আর বর্তমান Java-তে ডাকলে UnsupportedOperationException ছোড়ে। Thread.suspend / resume-এও একই। থ্রেড থামানোর একমাত্র বৈধ উপায় interruption দিয়ে সহযোগী থামানো। ExecutorService ব্যবহার করলে shutdown শুধু নতুন কাজ নেওয়া বন্ধ করে সম্পূর্ণতার অপেক্ষা করে — চলন্ত কাজে interrupt পাঠায় না। চলন্ত কাজ থামানোর চেষ্টা করে shutdownNow, আর তা best-effort (মানক বাস্তবায়ন interruption দিয়ে কাজ করে)।