volatile: শুধুমাত্র ভিজিবিলিটি-র জন্য কীওয়ার্ড

আগের পর্বে একটি ওয়ার্কার থ্রেড অনন্তকাল ঘুরতে থাকে, কারণ সে অন্য থ্রেডের লেখা সাধারণ boolean stop ফিল্ডটি কখনোই দেখতে পায়নি। সেই ফিল্ডটিকে volatile চিহ্নিত করলেই সরাসরি সমস্যাটি ঠিক হয়ে যায়। volatile ঠিক কী গ্যারান্টি দেয় আর কী দেয় না, সেটা বুঝতে পারলে কনকারেন্সি বাগের সাধারণ সমাধান হিসেবে এটাকে ব্যবহার করার ভুল এড়ানো যায়।

ভিজিবিলিটি হ্যাং সমাধান করা

public class VolatileFix {
    private static volatile boolean stop = false;

    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            long iterations = 0;
            while (!stop) {
                iterations++;
            }
            System.out.println("Stopped after " + iterations + " iterations");
        });

        worker.start();
        Thread.sleep(1000);
        stop = true;
        System.out.println("main set stop = true");
    }
}

একটি volatile ফিল্ডে করা প্রতিটি লেখা পরবর্তীতে যেকোনো থ্রেডের পড়ার সময় তাৎক্ষণিকভাবে দৃশ্যমান হওয়ার গ্যারান্টি পাওয়া যায়। কম্পাইলার আর ফিল্ডের মান রেজিস্টারে ক্যাশে রাখতে বা লুপের বাইরে রিড সরিয়ে নিতে পারে না, যেমনটা সাধারণ ফিল্ডের ক্ষেত্রে করতে পারে। এই একটি পরিবর্তনই গ্যারান্টি দেয় যে worker থ্রেড শেষ পর্যন্ত stop == true দেখতে পাবে এবং লুপ থেকে বেরিয়ে আসবে।

volatile যা করে না: অ্যাটমিসিটি

volatile শুধুমাত্র একটি একক ফিল্ডের পড়া ও লেখার জন্য happens-before সম্পর্ক স্থাপন করে। এটি ওই ফিল্ডের ওপর read-modify-write সিকোয়েন্সকে অ্যাটমিক করে না:

public class VolatileIsNotAtomic {
    private static volatile int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 100_000; i++) {
                count++; // এখনও পড়া, এক যোগ করা, আবার লেখা – তিনটি আলাদা ধাপ
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("Expected 200000, got " + count); // সাধারণত কম পাওয়া যায়
    }
}

count-কে volatile ঘোষণা করলে প্রতিটি থ্রেড যখনই count পড়ে তখন সর্বশেষ মান দেখতে পায়। কিন্তু count++ এখনও তিনটি পৃথক ধাপ, এবং দুটি থ্রেড একই সময়ে এই ধাপগুলোকে ইন্টারলিভ করে একটি আপডেট হারিয়ে ফেলতে পারে, ঠিক আগের পর্বের নন-ভোলাটাইল সংস্করণের মতোই। এটি volatile নিয়ে সবচেয়ে প্রচলিত ভুল ধারণা: এটি একটি একক পড়া বা একক লেখার জন্য ভিজিবিলিটি গ্যারান্টি দেয়, কোনো অপারেশন যখন একটি মান পড়ে তার ভিত্তিতে নতুন মান লেখে তখন synchronized বা অ্যাটমিক ক্লাসের বিকল্প নয়।

যখন volatile ঠিক সঠিক টুল

volatile তখনই সম্পূর্ণ ও সঠিক সমাধান যখন একটি ফিল্ড শুধুমাত্র একটি থ্রেড লেখে এবং অন্য থ্রেডগুলো কেবল পড়ে (কখনোই read-then-write করে না)। এটি অবিকল স্টপ ফ্ল্যাগ, স্ট্যাটাস ফ্ল্যাগ, অথবা কোনো ইমিউটেবল অবজেক্টের রেফারেন্সের আকৃতি—যেখানে সম্পূর্ণ অবজেক্টটি বদলে ফেলা হয়, ইন-প্লেস মিউটেট করা হয় না:

public class Config {
    private final String dbUrl;
    private final int maxConnections;

    public Config(String dbUrl, int maxConnections) {
        this.dbUrl = dbUrl;
        this.maxConnections = maxConnections;
    }

    public String getDbUrl() {
        return dbUrl;
    }

    public int getMaxConnections() {
        return maxConnections;
    }

    public static Config defaults() {
        return new Config("jdbc:mysql://localhost:3306/mydb", 10);
    }
}

public class ConfigHolder {
    private static volatile Config current = Config.defaults();

    public static Config get() {
        return current; // সর্বদা সর্বশেষ প্রকাশিত Config দেখতে পায়
    }

    public static void reload(Config newConfig) {
        current = newConfig; // সম্পূর্ণ রেফারেন্স অ্যাটমিকভাবে বদলায়
    }

    public static void main(String[] args) {
        System.out.println("Current max connections: " + get().getMaxConnections());
        Config newConfig = new Config("jdbc:mysql://newhost:3306/mydb", 20);
        reload(newConfig);
        System.out.println("After reload: " + get().getMaxConnections());
    }
}

এই ডিজাইনে Config ইমিউটেবল, তাই একবার তৈরি হয়ে গেলে এর অভ্যন্তরীণ অবস্থা কখনো বদলায় না। ConfigHolder.current ফিল্ডটি volatile হওয়ায় reload() কল করার পর অন্য যেকোনো থ্রেড get() চালালে নতুন Config অবজেক্টটি নিশ্চিতভাবে দেখতে পায়। এখানে কোনো read-modify-write নেই, শুধু একটি পূর্ণাঙ্গ রেফারেন্স অ্যাসাইনমেন্ট, তাই volatile সম্পূর্ণ নিরাপদ এবং যথেষ্ট।

Share