রিঅর্ডারিং ও জাভা মেমোরি মডেল
একটি কম্পাইলার এবং একটি সিপিইউ উভয়ই কোনো প্রোগ্রামের নির্দেশনা সোর্স কোডে যে ক্রমে আছে তার চেয়ে ভিন্ন ক্রমে সম্পাদন করতে পারে, এবং মানগুলো রেজিস্টার বা স্টোর বাফারে ধরে রাখতে পারে মেইন মেমোরিতে সঙ্গে সঙ্গে লেখার পরিবর্তে — যতক্ষণ না এর কোনোটিই একক থ্রেড একা চলার সময় যে ফলাফল দেখে তা পরিবর্তন করে। এই গ্যারান্টি, অর্থাৎ একটি বিচ্ছিন্ন থ্রেডের জন্য সিকোয়েনশিয়াল কনসিসটেন্সি, মূলত একমাত্র অর্ডারিং প্রতিশ্রুতি যা জেভিএম বিনামূল্যে দেয়। একই মেমোরি পর্যবেক্ষণরত দ্বিতীয় কোনো থ্রেড কী দেখতে পাবে, সে বিষয়ে এটি কিছুই বলে না। জাভা মেমোরি মডেল নির্দিষ্টভাবে সেই সংকীর্ণ পরিস্থিতিগুলো সংজ্ঞায়িত করার জন্য তৈরি করা হয়েছে যেখানে ক্রস-থ্রেড অর্ডারিং নিশ্চিত করা হয়, যাকে এটি হ্যাপেনস-বিফোর সম্পর্ক বলে; বাকি সবকিছু রিঅর্ডারিংয়ের জন্য উন্মুক্ত।
আংশিকভাবে তৈরি অবজেক্টের অকাল প্রকাশ
এই রিঅর্ডারিংয়ের একটি সবচেয়ে মূর্ত ও পুনরুৎপাদনযোগ্য পরিণতি হলো অনিরাপদ প্রকাশনা: কোনো অবজেক্টের কনস্ট্রাক্টর আসলে কাজ শেষ করার আগেই তার রেফারেন্স অন্য থ্রেডের কাছে দৃশ্যমান হয়ে যাওয়া।
public class UnsafePublication {
private int value;
private static UnsafePublication instance;
public UnsafePublication(int value) {
this.value = value;
instance = this; // নির্মাণকাজ "চলমান" থাকতেই 'this' বাইরে চলে যায়
}
public int getValue() {
return value;
}
public static void main(String[] args) {
new Thread(() -> new UnsafePublication(42)).start();
while (instance == null) {
Thread.onSpinWait();
}
System.out.println("value = " + instance.getValue());
}
}
উপরের কোডের উদ্দেশ্য সোজা মনে হয়: প্রথমে value সেট করা, তারপর this-কে একটি স্ট্যাটিক ফিল্ডে প্রকাশ করা, যাতে যে থ্রেডই instance-কে নন-নাল দেখবে সে value == 42-ও দেখবে। কিন্তু পঠনরত থ্রেডের দৃষ্টিকোণ থেকে value-তে লেখা এবং instance-এ লেখার মধ্যে কোনো হ্যাপেনস-বিফোর সম্পর্ক নেই। এটি সম্পূর্ণ বৈধ, এবং বাস্তব হার্ডওয়্যারে মাঝে মাঝে পর্যবেক্ষণযোগ্যও, যে পঠনরত থ্রেড instance-কে নন-নাল দেখতে পায় কিন্তু getValue() তখনও 0 (ফিল্ডের ডিফল্ট মান) রিটার্ন করে, কারণ কনস্ট্রাক্টরের ভেতরের দুটি লেখা অন্য থ্রেডের কাছে প্রোগ্রাম অর্ডারে দৃশ্যমান হওয়ার কোনো নিশ্চয়তা নেই। এই কারণেই কনস্ট্রাক্টর যেখানে this-কে পালাতে দেয় (লিসেনার নিবন্ধন, থ্রেড শুরু করা, বা নির্মাণ শেষ হওয়ার আগে স্ট্যাটিক ফিল্ডে অ্যাসাইন করার মাধ্যমে) তা পুনরুৎপাদন করা কঠিন বাগের একটি সুপরিচিত উৎস, এবং সম্পূর্ণরূপে তৈরি হওয়ার পরই প্রকাশিত ইমিউটেবল অবজেক্ট পুরো সমস্যাটি এড়ানোর অন্যতম সহজ উপায়।
এটি কেন বৈধ, জেভিএমের ত্রুটি নয়
এটিকে জেভিএমের সংশোধন করা উচিত এমন কোনো বাগ হিসেবে ভাবা বন্ধ করা জরুরি। রিঅর্ডারিং এবং বিলম্বিত দৃশ্যমানতাই কম্পাইলারকে ইনস্ট্রাকশন পাইপলাইন অপটিমাইজ করতে এবং সিপিইউকে প্রতিটি লেখার জন্য মেইন মেমোরিতে না গিয়ে প্রতি-কোর ক্যাশ ও স্টোর বাফার ব্যবহার করতে দেয় — যা পারফরম্যান্সের জন্য অপরিহার্য। জাভা মেমোরি মডেলের কাজ এটি নিষিদ্ধ করা নয়; বরং এটি আপনাকে সঠিকভাবে জানানো যে কোন ক্রিয়াগুলোর ক্রম দৃশ্যমানতার ওপর আপনি নির্ভর করতে পারেন — synchronized, volatile, এবং পরবর্তী আলোচনায় আসা আরও কিছু নির্দিষ্ট নির্মাণের মাধ্যমে।