সিঙ্ক্রোনাইজার: CountDownLatch, CyclicBarrier, এবং Semaphore
এতক্ষণ যে টুলগুলো আমরা দেখেছি সেগুলো শেয়ার্ড স্টেট রক্ষা করে: লক, অ্যাটমিক ক্লাস, এবং কনকারেন্ট কালেকশন—এরা সব নিশ্চিত করে যে একই ডেটা দুটি থ্রেড একসঙ্গে নষ্ট না করে। সিঙ্ক্রোনাইজাররা ভিন্ন একটি সমস্যার সমাধান করে: থ্রেডগুলোকে সঠিক মুহূর্তে একে অপরের জন্য অপেক্ষা করানো, তা তারা কোনো শেয়ার্ড ডেটা স্পর্শ করুক বা নাই করুক।
CountDownLatch: N সংখ্যক কাজ শেষ হওয়ার জন্য একবার অপেক্ষা
CountDownLatch এক বা একাধিক থ্রেডকে তখন পর্যন্ত ব্লক করে রাখে যতক্ষণ না নির্দিষ্ট সংখ্যক অন্য থ্রেড কাজ শেষ করার সংকেত দেয়:
import java.util.concurrent.CountDownLatch;
public class StartupLatch {
public static void main(String[] args) throws InterruptedException {
int workerCount = 3;
CountDownLatch ready = new CountDownLatch(workerCount);
for (int i = 1; i <= workerCount; i++) {
int id = i;
new Thread(() -> {
System.out.println("Worker " + id + " initializing");
ready.countDown(); // সংকেত দেয় যে এই ওয়ার্কার প্রস্তুত
}).start();
}
ready.await(); // যতক্ষণ না তিনজনই countDown() কল করে ততক্ষণ ব্লক থাকবে
System.out.println("All workers ready, proceeding");
}
}
এখানে একটি CountDownLatch তৈরি করা হয়েছে ৩টি গণনা দিয়ে। তিনটি থ্রেড প্রত্যেকে কাজ শেষে countDown() কল করলে গণনা এক ধাপ করে কমে। মূল থ্রেড await()-এ আটকে থাকে; গণনা শূন্য হলে তবেই তা ছেড়ে দেয় এবং “All workers ready” প্রিন্ট করে। একবার শূন্যে পৌঁছালে ল্যাচটি আর রিসেট করা যায় না—পরবর্তী সব await() কল তাৎক্ষণিকভাবে ফেরত আসে।
এটি এককালীন ইভেন্টের জন্য আদর্শ, যেমন নির্দিষ্ট সংখ্যক স্টার্টআপ টাস্ক শেষ হওয়ার অপেক্ষা করা, অথবা প্যারালাল সাবটাস্কগুলো শেষ হলে তাদের ফলাফল একত্রিত করার আগে অপেক্ষা করা।
CyclicBarrier: N সংখ্যক থ্রেডের জন্য বারবার অপেক্ষা
CyclicBarrier দেখতে একই রকম মনে হলেও এটি ভিন্ন সমস্যার সমাধান করে: একটি নির্দিষ্ট থ্রেড-গোষ্ঠীর সবাইকে একটি নির্দিষ্ট বিন্দুতে একে অপরের জন্য অপেক্ষা করানো, তারপর সবাইকে একসঙ্গে ছেড়ে দেওয়া, এবং পরবর্তী রাউন্ডে আবার একই কাজ করা:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class RoundBarrier {
public static void main(String[] args) {
int workerCount = 3;
CyclicBarrier barrier = new CyclicBarrier(workerCount,
() -> System.out.println("--- round complete ---"));
for (int i = 1; i <= workerCount; i++) {
int id = i;
new Thread(() -> {
for (int round = 1; round <= 2; round++) {
System.out.println("Worker " + id + " finished round " + round);
try {
barrier.await(); // এখানে অপেক্ষা করবে যতক্ষণ না তিনজনই পৌঁছায়
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
}
প্রতিটি থ্রেড barrier.await()-এ থামে। যখন তিনজনই পৌঁছে যায়, তখন ঐচ্ছিক রানেবল অ্যাকশনটি একবার চালিত হয় এবং তিনজনকেই একসঙ্গে ছেড়ে দেওয়া হয় পরবর্তী রাউন্ড শুরু করার জন্য। CountDownLatch-এর বিপরীতে এটি স্বয়ংক্রিয়ভাবে রিসেট হয় এবং পুনরায় ব্যবহার করা যায়। এটি সিমুলেশন বা প্যারালাল অ্যালগরিদমের জন্য দারুণ, যেখানে প্রতিটি ধাপে সব ওয়ার্কারকে শেষ করতে হয় তার আগে কেউ পরবর্তী ধাপ শুরু করতে পারবে না।
Semaphore: কনকারেন্ট রিসোর্স অ্যাক্সেস সীমিত করা
Semaphore একটি নির্দিষ্ট সংখ্যক পারমিট ধারণ করে। একটি থ্রেড acquire() কল করে একটি পারমিট নেয় (পারমিট না থাকলে ব্লক হয়), এবং কাজ শেষে release() কল করে পারমিটটি ফেরত দেয়:
import java.util.concurrent.Semaphore;
public class ResourcePool {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2); // একসঙ্গে সর্বোচ্চ ২টি থ্রেড প্রবেশ করতে পারবে
for (int i = 1; i <= 5; i++) {
int id = i;
new Thread(() -> {
try {
semaphore.acquire(); // একটি পারমিট নেওয়ার চেষ্টা
System.out.println("Thread " + id + " acquired permit");
Thread.sleep(1000); // রিসোর্স ব্যবহারের সিমুলেশন
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println("Thread " + id + " releasing permit");
semaphore.release(); // পারমিট ফেরত দেওয়া
}
}).start();
}
}
}
এখানে Semaphore তৈরি করা হয়েছে ২টি পারমিট নিয়ে। পাঁচটি থ্রেড একটি সীমিত রিসোর্স ব্যবহারের চেষ্টা করে, কিন্তু একসঙ্গে মাত্র দুটি থ্রেড acquire() করতে পারে। বাকিরা ব্লক থাকে যতক্ষণ না কোনো থ্রেড release() করে পারমিট ফেরত দেয়। finally ব্লকের ভেতর release() কল করলে নিশ্চিত হওয়া যায় যে ব্যতিক্রম ঘটলেও পারমিট ফেরত আসবে।
এটি সংযোগ পুল, থ্রেটলিং, বা এমন যেকোনো পরিস্থিতিতে ব্যবহার করা যায় যেখানে একটি সীমিত রিসোর্সে একসঙ্গে অনেক থ্রেডের প্রবেশ নিয়ন্ত্রণ করতে হয়।