কনকারেন্ট কালেকশন: ConcurrentHashMap ও তার সঙ্গীরা
পূর্ববর্তী পর্বের অ্যাটমিসিটি নিবন্ধে দেখা গেছে, কনকারেন্ট অ্যাক্সেসের সময় একটি HashMap-ভিত্তিক ভিজিট কাউন্টার আপডেট হারাচ্ছে। সমস্যা আসলে আরও গুরুতর: একাধিক থ্রেড থেকে বাহ্যিক সিঙ্ক্রোনাইজেশন ছাড়া একটি সাধারণ HashMap-এ মিউটেশন করলে এর অভ্যন্তরীণ কাঠামো নষ্ট হতে পারে, যা ভুল সংখ্যার বদলে অসীম লুপ বা ConcurrentModificationException উৎপন্ন করতে পারে। java.util.concurrent প্যাকেজ এই পরিস্থিতির জন্য নকশা করা কালেকশন সরবরাহ করে।
ConcurrentHashMap: নিরাপদ ও প্রতি কী-তে অ্যাটমিক অপারেশন
import java.util.concurrent.ConcurrentHashMap;
public class VisitCounter {
private final ConcurrentHashMap visits = new ConcurrentHashMap<>();
public void recordVisit(String page) {
visits.merge(page, 1, Integer::sum); // এই কী-তে অ্যাটমিক read-modify-write
}
public static void main(String[] args) throws InterruptedException {
VisitCounter counter = new VisitCounter();
Runnable task = () -> {
for (int i = 0; i < 10_000; i++) {
counter.recordVisit("home");
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter.visits.get("home")); // নির্ভরযোগ্যভাবে 20000
}
}
merge() মেথডটি লুকআপ, যোগ এবং রাইট-ব্যাক একটি একক অ্যাটমিক অপারেশন হিসেবে সম্পাদন করে, যা পূর্ববর্তী পর্বে সাধারণ HashMap-এর চেক-দেন-অ্যাক্ট ফাঁকটিই ভেঙেছিল। ConcurrentHashMap পুরো ম্যাপ লক না করেই এটি অর্জন করে; অভ্যন্তরীণভাবে এটি টেবিলের বিভিন্ন অংশে লকিং ভাগ করে দেয়, ফলে সম্পর্কহীন কীগুলিতে সাধারণত বিভিন্ন থ্রেড ন্যূনতম প্রতিযোগিতায় একসাথে আপডেট করতে পারে।
ConcurrentModificationException ছাড়া ইটারেশন
একটি সাধারণ HashMap-এর ইটারেটর যদি ইটারেট করার সময় ম্যাপে পরিবর্তন হয়, এমনকি একই থ্রেড থেকেও, তাহলে ConcurrentModificationException ছোঁড়ে। ConcurrentHashMap-এর ইটারেটরগুলি দুর্বলভাবে সঙ্গত (weakly consistent): এগুলি কখনোই সেই ব্যতিক্রম ছোঁড়ে না, এবং ইটারেশন চলাকালে ম্যাপের কোনো এক সময়ের অবস্থা প্রতিফলিত করার নিশ্চয়তা দেয়, তবে ইটারেশনের সময় হওয়া প্রতিটি পরিবর্তন অন্তর্ভুক্ত নাও হতে পারে। এটি স্ন্যাপশটের চেয়ে ভিন্ন একটি সঙ্গতি গ্যারান্টি, এবং যে কোড ধরে নেয় যে ইটারেট করার সময় ম্যাপের একটি স্থির দৃশ্য দেখছে, তারা বিভ্রান্ত হবে।
CopyOnWriteArrayList: পড়া-ভারী, বিরল-লেখার তালিকার জন্য নিরাপদ
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListenerRegistry {
private final List listeners = new CopyOnWriteArrayList<>();
public void addListener(Runnable listener) {
listeners.add(listener); // সম্পূর্ণ ব্যাকিং অ্যারে কপি করে
}
public void fireAll() {
for (Runnable listener : listeners) { // কখনো ConcurrentModificationException ছোঁড়ে না
listener.run();
}
}
}
CopyOnWriteArrayList প্রতিটি লেখার সময় পুরো ব্যাকিং অ্যারেটি কপি করে, যা শুনতে ব্যয়বহুল এবং লেখার কাজ বেশি হলে সত্যিই ব্যয়বহুল। এই খরচের বিনিময়ে এমন একটি ইটারেটর পাওয়া যায় যার কখনো কোনো বাহ্যিক সিঙ্ক্রোনাইজেশনের প্রয়োজন হয় না।