1. ما هو وضع Singleton
يشير نمط Singleton إلى وجود مثيل واحد فقط طوال حياة التطبيق. نمط Singleton هو نمط تصميم يستخدم على نطاق واسع. له العديد من الفوائد ، والتي يمكن أن تتجنب إنشاء كائنات مثيل مكررة ، وتقليل النظام النفقات العامة لإنشاء مثيلات ، وحفظ الذاكرة.
هناك ثلاثة متطلبات لوضع Singleton:
2. الفرق بين نمط المفرد والطبقة الثابتة
أولاً ، دعونا نفهم ماهية الفئة الثابتة. فئة ثابتة تعني أن الفصل لديه طرق ثابتة وحقول ثابتة. يتم تعديل المنشئ بواسطة الخاص ، لذلك لا يمكن إنشاء مثيل له. فئة الرياضيات هي فئة ثابتة.
بعد معرفة ماهية الفئة الثابتة ، دعنا نتحدث عن الفرق بينهما:
1) أولاً وقبل كل شيء ، سوف يوفر لك نمط Singleton كائنًا فريدًا عالميًا. يوفر لك الفئة الثابتة فقط العديد من الطرق الثابتة. لا تحتاج هذه الطرق إلى إنشاء ، ويمكن استدعاؤها مباشرة من خلال الفصل ؛
2) نمط Singleton له مرونة أعلى ، ويمكن تجاوز الأساليب ، لأن الطبقات الثابتة كلها طرق ثابتة ، لذلك لا يمكن تجاوزها ؛
3) إذا كان كائنًا ثقيلًا جدًا ، فقد يكون نمط Singleton كسولًا للتحميل ، ولكن لا يمكن للفصول الثابتة القيام بذلك ؛
ثم ، يجب استخدام الفصول الساكنة ، ومتى يجب أن نستخدم وضع Singleton؟ بادئ ذي بدء ، إذا كنت ترغب فقط في استخدام بعض طرق الأدوات ، فمن الأفضل استخدام فئات ثابتة. التقييمات الثابتة أسرع من فصول المفرد ، لأنه يتم تنفيذ الربط الثابت خلال فترة التجميع. إذا كنت ترغب في الحفاظ على معلومات الحالة أو الوصول إلى الموارد ، فيجب عليك استخدام وضع Singleton. يمكن القول أيضًا أنه عندما تحتاج إلى إمكانات موجهة نحو الكائنات (مثل الميراث ، تعدد الأشكال) ، اختر فصول Singleton ، وعندما تقدم بعض الطرق فقط ، اختر فصولًا ثابتة.
3. كيفية تنفيذ وضع Singleton
1. وضع الرجل الجائع
ما يسمى الوضع الجائع هو التحميل على الفور. بشكل عام ، تم إنشاء الحالات قبل استدعاء طريقة getInstanceF ، مما يعني أنه قد تم إنشاؤه عند تحميل الفصل. عيب هذا النموذج واضح للغاية ، وهو أنه يشغل الموارد. عندما تكون فئة Singleton كبيرة ، نريد بالفعل استخدامه ثم إنشاء مثيلات. لذلك ، هذه الطريقة مناسبة للفئات التي تشغل موارد أقل وسيتم استخدامها أثناء التهيئة.
Class Singletonhungary {private static singletonhungary singletonhungary = new SingletonHungary () ؛ // قم بتعيين المُنشئ الخاص بحظر الاستئصال من خلال singletonhungary خاص جديد () {} singletonhungary getinstance () {return singletonhungary ؛ }}2. وضع كسول
الوضع الكسول هو التحميل كسول ، ويسمى أيضا التحميل كسول. قم بإنشاء مثيل عند الحاجة إلى استخدام البرنامج ، حتى لا تضيع الذاكرة. بالنسبة للوضع الكسول ، إليك 5 طرق تنفيذ. بعض أساليب التنفيذ هي عجز الخيوط ، مما يعني أن مشاكل مزامنة الموارد قد تحدث في بيئة تزامن متعددة الخيوط.
بادئ ذي بدء ، الطريقة الأولى هي أنه لا توجد مشكلة في موضوع واحد ، ولكن ستكون هناك مشاكل في متعدد الخيوط.
// التنفيذ الكسول لوضع Singleton 1-thread فئة غير آمنة SingletonLazy1 {private Static SingletonLazy1 SingletonLazy ؛ خاص singletonlazy1 () {} static singletonlazy1 getInstance () {if (null == singletonlazy) {try {// محاكاة بعض التحضير قبل إنشاء thread.sleep (1000) ؛ } catch (interruptedException e) {E.PrintStackTrace () ؛ } singletonLazy = New SingletonLazy1 () ؛ } إرجاع singletonlazy ؛ }}دعنا نحاكي 10 خيوط غير متزامنة للاختبار:
الطبقة العامة singletonlazytest {public static void main (string [] args) {thread2 [] threadarr = new thread2 [10] ؛ لـ (int i = 0 ؛ i <froofarr.length ؛ i ++) {threadarr [i] = new thread2 () ؛ ThreadArr [i] .start () ؛ }) }}نتائج التشغيل:
124191239
124191239
872096466
1603289047
1698032342
1913667618
371739364
124191239
1723650563
367137303
يمكنك أن ترى أن رموزها ليست متشابهة ، مما يعني أن الكائنات المتعددة يتم إنشاؤها في بيئة متعددة الخيوط ، والتي لا تفي بمتطلبات نمط المفرد.
فكيف تجعل الموضوع آمنًا؟ في الطريقة الثانية ، نستخدم الكلمة الرئيسية المتزامنة لمزامنة طريقة getInstance.
// Singleton Mode Lazy Application 2-Safety // من خلال تحديد طريقة التزامن ، تكون الكفاءة منخفضة للغاية ، والطريقة بأكملها هي SingletonLazy2 {Private Static SingletonLazy2 SingletonLazy ؛ خاص singletonlazy2 () {} singletonlazy singletonlazy2 الثابتة العامة getInstance () {try {if (null == singletonlazy) {// محاكاة للقيام ببعض التحضير قبل إنشاء thread.sleep (1000) ؛ SingletonLazy = New SingletonLazy2 () ؛ }} catch (interruptedException e) {E.PrintStackTrace () ؛ } إرجاع singletonlazy ؛ }}باستخدام فئة الاختبار أعلاه ، نتائج الاختبار:
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
كما يتضح ، هذه الطريقة تحقق سلامة الخيط. ومع ذلك ، فإن العيب هو أن الكفاءة منخفضة للغاية وأنها تعمل بشكل متزامن. إذا أراد مؤشر الترابط التالي الحصول على الكائن ، فيجب أن ينتظر إصدار مؤشر الترابط السابق قبل أن يتمكن من الاستمرار في التنفيذ.
بعد ذلك ، لا يمكننا قفل الطريقة ، ولكن قفل الكود في الداخل ، والذي يمكن أن يحقق أيضًا سلامة مؤشرات الترابط. ولكن هذه الطريقة هي نفس طريقة التزامن ، كما أنها تعمل بشكل متزامن ولديها كفاءة منخفضة للغاية.
// SingletonLazy Applearation 3-Thread Safety // من خلال تعيين كتل التعليمات البرمجية المتزامنة ، تكون الكفاءة منخفضة للغاية ، وكتلة الكود بأكملها هي SingletonLazy3 {Private Static SingletonLazy3 SingletonLazy ؛ خاص singletonlazy3 () {} static singletonlazy3 getInstance () {try {synchronized (singletonlazy3.class) {if (null == singletonlazy) {// محاكاة للقيام ببعض التحضير قبل إنشاء thread.sleep (1000) ؛ SingletonLazy = New SingletonLazy3 () ؛ }}} catch (interruptedException e) {// todo: handled insport} return singletonlazy ؛ }}دعنا نستمر في تحسين الكود. نحن فقط قفل الكود الذي ينشئ الكائن ، ولكن هل يمكن أن يضمن هذا سلامة مؤشر الترابط؟
// التنفيذ البطيء لوضع Singleton 4-Thread Unfafe // فقط الكود الذي ينشئ مثيلات يتم مزامنته عن طريق تعيين كتل رمز المزامنة // ولكن لا تزال هناك مشكلات سلامة مؤشرات الترابط SingletonLazy4 {private Static SingletonLazy4 SingletonLazy ؛ خاص singletonlazy4 () {} SingletonLazy4 GetInstance () {try {if (null == singletonlazy) {// code 1 // محاكاة للقيام ببعض التحضير قبل إنشاء موضوع كائن. Synchronized (SingletonLazy4.class) {SingletonLazy = New SingletonLazy4 () ؛ // code 2}}} catch (InterruptedException e) {// todo: handled exception} return singletonlazy ؛ }}لنلقي نظرة على نتائج الجري:
1210004989
1425839054
1723650563
389001266
1356914048
389001266
1560241484
278778395
124191239
367137303
انطلاقًا من النتائج ، لا يمكن أن تضمن هذه الطريقة سلامة الموضوع. لماذا؟ دعنا نفترض أن ترابطين A و B ينتقلان إلى "الرمز 1" في نفس الوقت ، لأن الكائن لا يزال فارغًا في هذا الوقت ، لذلك يمكن كلاهما إدخال الطريقة. الخيط الأول يمسك القفل وإنشاء الكائن. بعد حصول الموضوع B على القفل ، سيذهب أيضًا إلى "Code 2" عند إصداره ، ويتم إنشاء كائن. لذلك ، لا يمكن ضمان المفرد في بيئة متعددة الخيوط.
دعنا نستمر في التحسين. نظرًا لوجود مشكلة في الطريقة أعلاه ، يمكننا فقط إصدار حكم فارغ في كتلة رمز التزامن. هذه الطريقة هي آلية قفل DCL Double Check.
// Slazy Man in Singleton Mode ينفذ الأمان 5-thread // عن طريق تعيين كتلة رمز التزامن ، استخدم آلية قفل DCL المزدوجة // باستخدام آلية قفل التحقق المزدوجة بنجاح يحل بنجاح من مشكلات انعدام الأمن والكفاءة في المواضيع التي يتم تنفيذها من قبل Man On Lazy in Singleton Mode // DCL هي أيضًا حل يستخدمه معظم MultithReading مع Singleton Mode/ عند إنشاء كائن SingletonLazy5 ، عند الحصول على كائن SingletonLazy5 ، ليست هناك حاجة للتحقق من قفل كتلة رمز التزامن والرمز اللاحق ، وإرجاع كائن SingletonLazy5 مباشرة // وظيفة الثانية إذا كان الحكم: حل مشاكل الأمن تحت القراءة المتعددة ، وهذا ، لضمان عدم وجود كائن. Class SingletonLazy5 {private Static Folatile SingletonLazy5 SingletonLazy ؛ خاص singletonlazy5 () {} static singletonlazy5 getInstance () {try {if (null == singletonlazy) {// محاكاة بعض التحضير قبل إنشاء thread.sleep (1000) ؛ Synchronized (SingletonLazy5.class) {if (null == singletonlazy) {singletonLazy = new SingletonLazy5 () ؛ }}}} catch (InterruptedException e) {} return singletonlazy ؛ }}نتائج التشغيل:
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
يمكننا أن نرى أن آلية قفل DCL Double Check تحل مشكلات سلامة الكفاءة والخيط في وضع Singleton Lazing Lazy. هذه هي الطريقة التي نستخدمها في أغلب الأحيان.
الكلمة الرئيسية المتقلبة
هنا لاحظت أنه عند تعريف SingletonLazy ، يتم استخدام الكلمة الرئيسية المتطايرة. هذا لمنع تعليمات من إعادة ترتيب. لماذا نحتاج إلى القيام بذلك؟ لنلقي نظرة على سيناريو:
يذهب الرمز إلى SingletonLazy = New SingletonLazy5 () ؛ يبدو أن هذه الجملة ، لكن هذه ليست عملية ذرية (إما يتم تنفيذها ، أو لا يتم تنفيذها ، ولا يمكن تنفيذ النصف). يتم تجميع هذه الجملة في 8 تعليمات تجميع ، ويتم حوالي 3 أشياء:
1. تخصيص الذاكرة لمثيل SingletonLazy5.
2. تهيئة منشئ SingletonLazy5
3. أشر كائن SingletonLazy إلى مساحة الذاكرة المخصصة (لاحظ أن هذا المثيل ليس فارغًا).
نظرًا لأن برنامج التحويل البرمجي Java يسمح للمعالج بتنفيذ Out-Outder (Outder-order) ، وترتيب ذاكرة التخزين المؤقت ، يسجل إلى كتابة الذاكرة الرئيسية في JMM (Java Memory Medel) قبل JDK1.5 ، لا يمكن ضمان ترتيب النقطتين الثانية والثالثة أعلاه. وهذا يعني أن أمر التنفيذ قد يكون 1-2-3 أو 1-3-2. إذا كان هذا هو الأخير ، وقبل تنفيذ 3 ولم يتم تنفيذ 2 ، يتم تبديله إلى الموضوع 2. في هذا الوقت ، قامت SingletonLazy بتنفيذ النقطة الثالثة في الموضوع الأول ، وأن SingletonLazy غير فارغ بالفعل ، لذا فإن الخيط اثنين يأخذ مباشرة Singletonlazy ، ثم يستخدمه بشكل طبيعي. علاوة على ذلك ، قد لا يتم العثور على هذا النوع من الأخطاء التي يصعب تتبعها وصعوبة إعادة إنتاجها في الأسبوع الأخير من تصحيح الأخطاء.
يوصى باستخدام طريقة الكتابة DCL لتنفيذ Singletons في العديد من الكتب والكتب المدرسية (بما في ذلك الكتب القائمة على الإصدارات السابقة من JDK1.4) ، لكنها في الواقع غير صحيحة تمامًا. في الواقع ، DCL ممكن في بعض اللغات (مثل C) ، اعتمادًا على ما إذا كان يمكن ضمان ترتيب خطوات 2 و 3. بعد JDK1.5 ، لاحظ المسؤول هذه المشكلة ، لذلك تم تعديل JMM وتم تجريد الكلمة الرئيسية المتطايرة. لذلك ، إذا كان JDK إصدارًا من 1.5 أو لاحقًا ، فأنت بحاجة فقط إلى إضافة الكلمة الرئيسية المتطايرة إلى تعريف SingletonLazy ، والتي يمكن أن تضمن قراءة SingletonLazy من الذاكرة الرئيسية في كل مرة ، ويمكن حظر إعادة الترتيب ، ويمكنك استخدام طريقة كتابة DCL لإكمال وضع Singleton. بالطبع ، سيؤثر التقلب على الأداء بشكل أو بآخر. أهم شيء هو أننا نحتاج أيضًا إلى التفكير في JDK1.42 والإصدارات السابقة ، وبالتالي فإن تحسين كتابة أنماط المفرد لا يزال مستمراً.
3. فئة داخلية ثابتة
استنادًا إلى الاعتبارات المذكورة أعلاه ، يمكننا استخدام فئات داخلية ثابتة لتنفيذ نمط Singleton ، الرمز هو كما يلي:
. } singletonstaticinner static static getInstance () {try {thread.sleep (1000) ؛ } catch (interruptedException e) {// todo catch catch e.printstacktrace () ؛ } إرجاع singletoninner.singletonstaticinner ؛ }}يمكن أن نرى أننا لا نقوم بشكل صريح بأي عمليات التزامن بهذه الطريقة ، فكيف يضمن سلامة الخيط؟ مثل وضع Hungry Man ، إنها ميزة تضمن JVM أنه لا يمكن تحميل الأعضاء الثابتين في الفصل إلا مرة واحدة ، بحيث يكون هناك كائن مثيل واحد فقط من مستوى JVM. لذا فإن السؤال هو ، ما هو الفرق بين هذه الطريقة ونموذج الرجل الجائع؟ أليس التحميل على الفور؟ في الواقع ، عندما يتم تحميل فئة ، لن يتم تحميل فئةه الداخلية في نفس الوقت. يتم تحميل فئة ، والتي تحدث متى وفقط إذا تم استدعاء أحد أعضائه الثابتة (المجالات الثابتة ، المُنشئين ، الأساليب الثابتة ، إلخ).
يمكن القول أن هذه الطريقة هي الحل الأمثل لتنفيذ نمط Singleton.
4. كتل الرمز الثابت
فيما يلي نمط تنفيذ كتلة رمز ثابت. تشبه هذه الطريقة الأولى ، وهي أيضًا نموذج رجل جائع.
// استخدم كتل التعليمات البرمجية الثابتة لتنفيذ Singleton Mode Class SingletonStaticBlock {Private Static SingletonStaticBlock SingletonStaticBlock ؛ ثابت {singletonStaticBlock = new SingletonStaticBlock () ؛ } SingletonStaticBlock getInstance () {return SingletonStaticBlock ؛ }}5. التسلسل والخروج
لماذا يوصي LZ بالتسلسل والتهول؟ لأنه على الرغم من أن وضع Singleton يمكن أن يضمن سلامة مؤشر الترابط ، إلا أنه سيتم إنشاء كائنات متعددة في حالة التسلسل والتخلص من التسلسل. قم بتشغيل فئة الاختبار التالية ،
الطبقة العامة singletonstaticinnersializetest {public static void main (string [] args) {try {singletonStaticerialize Serialize = singletonStaticInnerserialize.getInstance () ؛ system.out.println (serialize.hashCode ()) ؛ // serialize fileOutputStream fo = newOutputStream ("tem") ؛ ObjectOutputStream oo = new ObjectOutputStream (FO) ؛ oo.writeObject (Serialize) ؛ oo.close () ؛ fo.close () ؛ // deserialize fileInputStream fi = new FileInputStream ("tem") ؛ ObjectInputStream oi = new ObjectInputStream (FI) ؛ SingletonStaticerialize Serialize2 = (SingletonStaticerialize) oi.ReadObject () ؛ oi.close () ؛ fi.close () ؛ System.out.println (Serialize2.hashCode ()) ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ }}} // استخدم فئات داخلية مجهولة لتنفيذ نمط Singleton. عند مواجهة التسلسل والتسلسل ، لم يتم الحصول على نفس الحالة. Class SingletonStaticInnerserialize الأدوات التسلسلية { / *** 28 مارس 2018* / private Static Final Long SerialVersionuid = 1L ؛ Private Static Class Innerclass {private Static SingletonStaticInisterialize SingletonStaticerialize = New SingletonStaticerialize () ؛ } SingletonStaticInstaticinistatialize getInstance () {return innerclass.singletonstaticinistatialize ؛ } // كائن محمي readResolve () {// system.out.println ("تم استدعاء طريقة ReadResolve")يمكنك أن ترى:
865113938
1078694789
أظهرت النتائج أنه في الواقع حالتان مختلفتان كائنات ينتهكان نمط المفرد. فكيف تحل هذه المشكلة؟ يتمثل الحل في استخدام طريقة ReadResolve () في إزالة الإخلال ، وإزالة رمز التعليق أعلاه ، وتشغيله مرة أخرى:
865113938
تم استدعاء طريقة ReadResolve
865113938
والسؤال هو ، من هي الطريقة المقدسة لطريقة ReadResolve ()؟ في الواقع ، عندما تمرر JVM و "تجميع" كائن جديد من الذاكرة ، فإنه سيسمي تلقائيًا طريقة ReadResolve هذه لإرجاع الكائن الذي حددناه ، وضمان قواعد Singleton. يسمح ظهور ReadResolve () للمبرمجين بالتحكم في الكائنات التي تم الحصول عليها من خلال التخلص من تلقاء نفسها.
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.