1. فئة فردية على الطراز الجائع
}
مثيل Singleton ثابت خاص = New Singleton();
مفردة ثابتة خاصة getInstance(){
مثيل الإرجاع؛
}
}
الميزات: إنشاء مثيل للنمط الجائع مسبقًا، لا توجد مشكلة متعددة الخيوط في النمط البطيء، ولكن بغض النظر عما إذا كنا نسميها getInstance() أم لا، سيكون هناك مثيل في الذاكرة
2. الطبقة المفردة الداخلية
}
فئة خاصة سينجلتون هولدر () {
مثيل Singleton ثابت خاص = New Singleton();
}
مفردة ثابتة خاصة getInstance(){
إرجاع SingletonHoledr.instance;
}
}
الميزات: في الفصل الداخلي، يتم تنفيذ التحميل البطيء فقط عندما نستدعي getInstance () وسيتم إنشاء مثيل فريد في الذاكرة، كما أنه يحل مشكلة الخيوط المتعددة في النمط البطيء .
3. فئة سينجلتون كسول
}
مثيل Singleton ثابت خاص؛
getInstance المفردة العامة الثابتة () {
إذا (مثيل == فارغ){
مثيل الإرجاع = Singleton الجديد () ؛
}آخر{
مثيل الإرجاع؛
}
}
}
الميزات: في النمط البطيء، هناك خيطان A وB. عندما يعمل الخيط A إلى السطر 8، فإنه ينتقل إلى الخيط B. وعندما يعمل B أيضًا إلى السطر 8، تكون مثيلات كلا الخيطين فارغة، لذلك سيتم إنشاء مثالين . الحل هو المزامنة:
المزامنة ممكنة ولكنها غير فعالة:
}
مثيل Singleton ثابت خاص؛
getInstance () المفردة الثابتة العامة المتزامنة
إذا (مثيل == فارغ){
مثيل الإرجاع = Singleton الجديد () ؛
}آخر{
مثيل الإرجاع؛
}
}
}
لن يكون هناك خطأ في كتابة برنامج مثل هذا، لأن getInstance بأكمله عبارة عن "قسم حرج" بالكامل، لكن الكفاءة سيئة للغاية، لأن هدفنا في الواقع هو القفل فقط عند تهيئة المثيل لأول مرة، ثم احصل على المثيل عند استخدام المثيل، ليست هناك حاجة لمزامنة مؤشر الترابط على الإطلاق.
لذلك توصل الأشخاص الأذكياء إلى النهج التالي:
طريقة كتابة قفل التحقق المزدوج:
public static Singleton getSingle(){ // يمكن الحصول على الكائنات الخارجية من خلال هذه الطريقة
إذا (مفرد == فارغ){
متزامن (Singleton.class) {// يضمن أن كائنًا واحدًا فقط يمكنه الوصول إلى هذه الكتلة المتزامنة في نفس الوقت
إذا (مفرد == فارغ){
Single = new Singleton();
}
}
}
return Single; // إرجاع الكائن الذي تم إنشاؤه
}
}
الفكرة بسيطة للغاية، أي أننا نحتاج فقط إلى مزامنة (مزامنة) جزء الكود الذي يقوم بتهيئة المثيل بحيث يكون الكود صحيحًا وفعالًا.
هذه هي ما يسمى بآلية "القفل المزدوج" (كما يوحي الاسم).
لسوء الحظ، هذه الطريقة في الكتابة خاطئة في العديد من المنصات والمترجمين الأمثلين.
والسبب هو أن سلوك سطر مثيل التعليمات البرمجية = new Singleton() على مترجمين مختلفين لا يمكن التنبؤ به. يمكن للمترجم الأمثل تنفيذ المثيل = new Singleton() بشكل قانوني كما يلي:
1. مثيل = تخصيص الذاكرة للكيان الجديد
2. اتصل بمنشئ Singleton لتهيئة متغيرات عضو المثيل
تخيل الآن أن الخيطين A وB يستدعيان getInstance. يدخل مؤشر الترابط A أولاً ويتم طرده من وحدة المعالجة المركزية عند تنفيذ الخطوة 1. ثم يدخل مؤشر الترابط B، وما يراه B هو أن المثيل لم يعد فارغًا (تم تخصيص الذاكرة)، لذلك يبدأ في استخدام المثيل بثقة، ولكن هذا خطأ، لأنه في هذه اللحظة، لا تزال متغيرات أعضاء المثيل افتراضية القيمة، لم يكن لدى A الوقت الكافي لتنفيذ الخطوة 2 لإكمال تهيئة المثيل.
وبطبيعة الحال، يمكن للمترجم أيضا تنفيذ ذلك على النحو التالي:
1.درجة الحرارة = تخصيص الذاكرة
2. اتصل بمنشئ درجة الحرارة
3. المثيل = درجة الحرارة
إذا تصرف المترجم بهذه الطريقة، فيبدو أنه لا توجد مشكلة لدينا، لكن الحقيقة ليست بهذه البساطة، لأننا لا نستطيع معرفة كيفية قيام مترجم معين بذلك، لأن هذه المشكلة غير محددة في نموذج ذاكرة Java.
ينطبق قفل التحقق المزدوج على الأنواع الأساسية (مثل int). من الواضح أن النوع الأساسي لا يستدعي المنشئ.