দৃশ্যমানতা: কেন অন্য থ্রেড কখনোই আপনার আপডেট নাও দেখতে পারে
রেস কন্ডিশন তখন ঘটে যখন দুটি থ্রেড একই অপারেশনের ওপর পা ফেলে। দৃশ্যমানতা আরও সূক্ষ্ম সমস্যা: শুধু একটি থ্রেডই যদি শেয়ার্ড স্টেট লিখে, তবুও অন্য থ্রেড সেই লেখাটি সময়মতো দেখতে পাবে—বা আদৌ দেখতে পাবে—এমন কোনো নিশ্চয়তা নেই। এটি কোনো ক্যাশের খামখেয়ালি নয় যা এড়িয়ে চলতে হবে; জাভা মেমোরি মডেলের স্পেসিফিকেশন অনুযায়ী সিঙ্ক্রোনাইজেশনের অভাবে এটাই স্বাভাবিক, দলিলভুক্ত আচরণ।
একটি লুপ যা কখনো শেষ নাও হতে পারে
public class VisibilityHang {
private static 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");
}
}
অধিকাংশ জেভিএম-এ, নির্দিষ্ট জেআইটি অপটিমাইজেশন লেভেলে চালালে worker থ্রেড কখনোই তার লাইন প্রিন্ট করে না। main থ্রেড stop = true সেট করলেও প্রোগ্রাম ক্র্যাশ বা ব্লক হয় না, কিন্তু worker থ্রেড অনির্দিষ্টকাল ঘুরতে পারে, কারণ stop-এর তাজা মান পুনরায় মেমোরি থেকে পড়তে বাধ্য করার কোনো নিয়ম নেই। কম্পাইলার বৈধভাবে লক্ষ্য করতে পারে যে লুপের ভেতরে stop কখনো লেখা হয় না, এবং রিডকে লুপের বাইরে টেনে এনে while (!stop)-কে কার্যত while (true)-এ রূপান্তর করতে পারে worker-এর দৃষ্টিকোণ থেকে।
এটি ধীর ক্যাশের সমস্যা নয়
অনেকেই এটিকে “আপডেটটি এক সিপিইউ ক্যাশ থেকে আরেকটিতে যেতে সময় নিচ্ছে” ভেবে বসেন, যেন কিছুক্ষণ অপেক্ষা করলেই সমস্যা মিটে যাবে। এই মানসিক মডেল ভুল ও বিপজ্জনক, কারণ এটি ধারণা দেয় যে বাগটি শেষ পর্যন্ত নিজে থেকেই সেরে যাবে। বাস্তবে, লেখা ও পড়ার মধ্যে সংজ্ঞায়িত কোনো হ্যাপেনস-বিফোর সম্পর্ক না থাকলে জেভিএম স্পেসিফিকেশন মোটেও দাবি করে না যে আপডেটটি কখনো দৃশ্যমান হবে, যতক্ষণই অপেক্ষা করুন না কেন। সমাধান ধৈর্য নয়; সমাধান একটি হ্যাপেনস-বিফোর প্রান্ত তৈরি করা, যা পরবর্তী পর্যায়গুলোতে volatile, synchronized এবং এগুলোর ওপর নির্মিত java.util.concurrent ইউটিলিটির মাধ্যমে আসে।
দৃশ্যমানতার বাগ বনাম রেস কন্ডিশন
দুটো সমস্যা প্রায়ই গুলিয়ে ফেলা হয় কারণ এরা একই রকম কোডে দেখা দেয়, কিন্তু এরা ভিন্ন ব্যর্থতা। রেস কন্ডিশন প্রশ্ন করে কী মান শেষ পর্যন্ত জমা হয় যখন দুটি লেখা ওভারল্যাপ করে। দৃশ্যমানতার সমস্যা প্রশ্ন করে কখনো এবং কখন একটি থ্রেড এমন একটি মান দেখতে পায় যা ইতিমধ্যেই নিরাপদে অন্য থ্রেড লিখে ফেলেছে, কোনো ওভারল্যাপ ছাড়াই। উপরের VisibilityHang-এ কোনো রেস নেই: শুধু main থ্রেডই stop-এ লেখে। তবুও এটি ব্যর্থ হয়, কারণ worker-কে জানানোর কিছু নেই যে তাকে নতুন মান খুঁজতে হবে।
কেন এটি বিরল মনে হয়, যতক্ষণ না আর বিরল থাকে
অনেক ডেভেলপার বছর পেরিয়ে গেলেও বিশুদ্ধ দৃশ্যমানতার বাগে পড়েন না, কারণ অসংখ্য জিনিস অনিচ্ছাকৃতভাবে হ্যাপেনস-বিফোর প্রান্ত তৈরি করে ফেলে: কনসোলে প্রিন্ট করা, অপ্রাসঙ্গিক কারণে কোনো synchronized ব্লকে ঢোকা, অথবা জেআইটি-অপটিমাইজড কম্পাইলারের বদলে ইন্টারপ্রেটারের অধীনে চলা। সেই আকস্মিক নিরাপত্তা তখনই গায়েব হয়ে যায় যখন আপনি কোনো আকস্মিক হ্যাপেনস-বিফোর সম্পর্ক ছাড়াই জেআইটি কম্পাইলারের অধীনে কোড চালান—যেমন প্রোডাকশন পরিবেশে বা সিঙ্ক্রোনাইজেশনবিহীন কোডে।