تحديث الذاكرة قبل المزامنة؟
-
13-12-2019 - |
سؤال
مذكور في نموذج ذاكرة Java ما يلي:عندما يخرج مؤشر ترابط من كتلة متزامنة كجزء من تحرير الشاشة المرتبطة، يتطلب JMM مسح ذاكرة التخزين المؤقت للمعالج المحلي إلى الذاكرة الرئيسية.بصورة مماثلة، كجزء من الحصول على الشاشة عند الدخول إلى كتلة متزامنة، يتم إبطال ذاكرة التخزين المؤقت المحلية بحيث تذهب عمليات القراءة اللاحقة مباشرة إلى الذاكرة الرئيسية وليس ذاكرة التخزين المؤقت المحلية.
فلماذا في هذا الكود يجب أن أعلن أن المثيل متقلب لأنه عندما يدخل الخيط الثاني فإن كتلة المزامنة ستنتقل مباشرة إلى الذاكرة الرئيسية ؟؟
public final class MySingleton {
private static MySingleton instance = null;
private MySingleton() { }
public static MySingleton getInstance() {
if (instance == null) {
synchronized (MySingleton.class) {
if (instance == null) {
instance = new MySingleton();
}
}
}
return instance;
}
}
أعني أنه عندما يدخل مؤشر ترابط آخر إلى كتلة المزامنة ويقوم بإجراء الفحص الثاني، فمن المفترض أن يتم التحديث من الذاكرة الرئيسية كما هو مذكور.
المحلول
حالة السباق هي كالتالي
يرى الخيط أ
instance == NULL
ويقوم بتشغيل هذا الرمزinstance = new MySingleton();
.الكتابة إلىinstance
مرئي ولكن يكتب إلىMySingleton
ليس بعد.يرى الموضوع B
instance != NULL
ويبدأ العمل على سبيل المثال.يعمل مؤشر الترابط B الآن على كائن لا يمكن رؤية بنائه.
تحضير instance
volatile يحل المشكلة حيث أن مواصفات ذاكرة JDK، اعتبارًا من JDK5، تضمن أن الكتابة إلى الكائنات غير المتطايرة لن يتم رؤيتها خارج الترتيب فيما يتعلق بالكتابة إلى كائن متطاير.لذلك أي موضوع الذي يرى instance != NULL
يجب أن نرى المثيل نفسه.
نصائح أخرى
يجب عليك أن تعلن أنه متقلب وإلا فليس هناك ما يضمن أن مكالمتين لـ getInstance() ستعيد نفس المثيل.
ليس هناك ما يضمن الوصول إلى الذاكرة الرئيسية، فقط قيمة متسقة مع ذاكرة التخزين المؤقت.أي.جميع المواضيع سوف ترى نفس القيمة.
بالمناسبة:أنت تعلم بالطبع أن الأمر أكثر تعقيدًا بكثير مما هو مطلوب.كل ما تحتاجه هو
public enum MySingleton {
INSTANCE;
}
يفعل نفس الشيء كثيرًا.