[java] ما هي الطريقة الفعالة لتنفيذ نمط مفرد في جافا؟



Answers

اعتمادا على الاستخدام ، هناك العديد من الإجابات "الصحيحة".

منذ java5 ، أفضل طريقة للقيام بذلك هي استخدام التعداد:

public enum Foo {
   INSTANCE;
}

قبل java5 ، فإن الحالة الأكثر بسيطة هي:

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

دعنا نذهب عبر الكود. أولاً ، تريد أن يكون الفصل نهائيًا. في هذه الحالة ، استخدمت الكلمة الرئيسية final للسماح للمستخدمين بمعرفة أنها نهائية. ثم تحتاج إلى جعل المنشئ خاص لمنع المستخدمين من إنشاء Foo الخاصة بهم. يؤدي تجاهل استثناء من المنشئ إلى منع المستخدمين من استخدام الانعكاس لإنشاء Foo ثاني. ثم تقوم بإنشاء حقل private static final Foo لاستيعاب المثيل الوحيد ، وطريقة public static Foo getInstance() . تتأكد مواصفات Java من أن المنشئ يتم استدعاءه فقط عند استخدام الفئة لأول مرة.

عندما يكون لديك كائن كبير جدًا أو رمز بناء ثقيل ولديك أيضًا طرق أو حقول ثابتة أخرى يمكن الوصول إليها قبل الحاجة إلى مثيل ، ثم تحتاج فقط إلى استخدام التهيئة البطيئة.

يمكنك استخدام private static class لتحميل المثيل. سيبدو الرمز كما يلي:

public final class Foo {

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }
}

منذ السطر private static final Foo INSTANCE = new Foo(); يتم تنفيذ فقط عند استخدام فئة FooLoader بالفعل ، هذا يعتني باكتشاف البطيئة ، وهو مضمون أن يكون مؤشر ترابط آمنة.

عندما تريد أيضًا أن تكون قادرًا على إجراء تسلسل للعنصر ، يجب أن تتأكد من أن إلغاء التسلسل لن يؤدي إلى إنشاء نسخة.

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

readResolve() الطريقة readResolve() التأكد من أن المثيل الوحيد سيتم إرجاعه ، حتى عندما يتم إجراء تسلسل للكائن في تشغيل سابق readResolve() .

Question

ما هي الطريقة الفعالة لتنفيذ نمط مفرد في جافا؟




To achieve this ( TRUE Singleton) ,

  1. Make sure that your class is final . Others can't sub-class it and create one more instance
  2. Make your singleton object as private static final
  3. Provide private constructor and public getInstance() method.
  4. Make sure that this Singleton class is loaded by one ClassLoader only
  5. override readResolve( ) method and return same instance.

Useful links: All answers in this post +

Singleton_pattern : Initialization-on-demand holder idiom from wikipedia

journaldev article

Demonstration of various methods of achieving Singleton

public final class  Singleton{
    private static final Singleton instance = new Singleton();

    public static Singleton getInstance(){
        return instance; 
    }
    public enum EnumSingleton {
        INSTANCE;   
    }
    private Object readResolve()  {
        return instance;
    }
    public static void main(String args[]){
        System.out.println("Singleton:"+Singleton.getInstance());
        System.out.println("Enum.."+EnumSingleton.INSTANCE);
        System.out.println("Lazy.."+LazySingleton.getInstance());
    }
}
final class  LazySingleton {
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder {
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
    private Object readResolve()  {
        return LazyHolder.INSTANCE;
    }
}

انتاج:

Singleton:Singleton@5eb1404f
Enum..INSTANCE
Lazy..LazySingleton@46fb3d6



تحتوي ويكيبيديا على بعض examples على المفردات ، وأيضاً في جافا. يبدو تطبيق Java 5 مكتملًا تمامًا ، وهو آمن بخيط (تم تطبيق القفل المزدوج).




For JSE 5.0 and above take the Enum approach, otherwise use static singleton holder approach ( (a lazy loading approach described by Bill Pugh). Latter solution is also thread-safe without requiring special language constructs (ie volatile or synchronized).




تحتاج double-checking المصطلح إذا كنت بحاجة إلى تحميل متغير المثيل لفصل دراسي. إذا كنت في حاجة إلى تحميل متغير ثابت أو مفردة المفرد ، فأنت تحتاج إلى التهيئة على لغة حامل الطلب .

بالإضافة إلى ذلك ، إذا احتاج المفرد إلى أن يكون seriliazble ، يجب أن تكون جميع الحقول الأخرى عابرة ويجب أن يتم تنفيذ methodResolve () من أجل الحفاظ على كائن مفرد unariant. خلاف ذلك ، في كل مرة يتم إلغاء تسلسل الكائن ، سيتم إنشاء نسخة جديدة من الكائن. ما يقوم readResolve () باستبدال الكائن الجديد الذي يقرأ بواسطة readObject () ، والذي أجبر ذلك الكائن الجديد على أن يتم جمع البيانات المهملة حيث لا يوجد متغير يشير إليها.

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 



ننسى التهيئة البطيئة ، إنها إشكالية للغاية. هذا هو أبسط حل:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

    public static A getInstance() {
        return INSTANCE;
    }
}



التعداد المفرد

تستخدم أبسط طريقة لتطبيق Singleton الآمن مؤشر ترابط التعداد

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

يعمل هذا الرمز منذ إدخال Enum في Java 1.5

فحص مزدوج قفل

If you want to code a “classic” singleton that works in a multithreaded environment (starting from Java 1.5) you should use this one.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class){
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance ;
  }
}

This is not thread-safe before 1.5 because the implementation of the volatile keyword was different.

Early loading Singleton (works even before Java 1.5)

This implementation instantiates the singleton when the class is loaded and provides thread safety.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}



Might be a little late to the game on this, but there is a lot of nuance around implementing a singleton. The holder pattern can not be used in many situations. And IMO when using a volatile - you should also use a local variable. Let's start at the beginning and iterate on the problem. You'll see what I mean.

The first attempt might look something like this:

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

Here we have the MySingleton class which has a private static member called INSTANCE, and a public static method called getInstance(). The first time getInstance() is called, the INSTANCE member is null. The flow will then fall into the creation condition and create a new instance of the MySingleton class. Subsequent calls to getInstance() will find that the INSTANCE variable is already set, and therefore not create another MySingleton instance. This ensures there is only one instance of MySingleton which is shared among all callers of getInstance().

But this implementation has a problem. Multi-threaded applications will have a race condition on the creation of the single instance. If multiple threads of execution hit the getInstance() method at (or around) the same time, they will each see the INSTANCE member as null. This will result in each thread creating a new MySingleton instance and subsequently setting the INSTANCE member.

private static MySingleton INSTANCE;

public static synchronized MySingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new MySingleton();
    }

    return INSTANCE;
}

Here we've used the synchronized keyword in the method signature to synchronize the getInstance() method. This will certainly fix our race condition. Threads will now block and enter the method one at a time. But it also creates a performance problem. Not only does this implementation synchronize the creation of the single instance, it synchronizes all calls to getInstance(), including reads. Reads do not need to be synchronized as they simply return the value of INSTANCE. Since reads will make up the bulk of our calls (remember, instantiation only happens on the first call), we will incur an unnecessary performance hit by synchronizing the entire method.

private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

Here we've moved synchronization from the method signature, to a synchronized block that wraps the creation of the MySingleton instance. But does this solve our problem? Well, we are no longer blocking on reads, but we've also taken a step backward. Multiple threads will hit the getInstance() method at or around the same time and they will all see the INSTANCE member as null. They will then hit the synchronized block where one will obtain the lock and create the instance. When that thread exits the block, the other threads will contend for the lock, and one by one each thread will fall through the block and create a new instance of our class. So we are right back where we started.

private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Here we issue another check from INSIDE the block. If the INSTANCE member has already been set, we'll skip initialization. This is called double-checked locking.

This solves our problem of multiple instantiation. But once again, our solution has presented another challenge. Other threads might not “see” that the INSTANCE member has been updated. This is because of how Java optimizes memory operations. Threads copy the original values of variables from main memory into the CPU's cache. Changes to values are then written to, and read from, that cache. This is a feature of Java designed to optimize performance. But this creates a problem for our singleton implementation. A second thread — being processed by a different CPU or core, using a different cache — will not see the changes made by the first. This will cause the second thread to see the INSTANCE member as null forcing a new instance of our singleton to be created.

private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

We solve this by using the volatile keyword on the declaration of the INSTANCE member. This will tell the compiler to always read from, and write to, main memory, and not the CPU cache.

But this simple change comes at a cost. Because we are bypassing the CPU cache, we will take a performance hit each time we operate on the volatile INSTANCE member — which we do 4 times. We double-check existence (1 and 2), set the value (3), and then return the value (4). One could argue that this path is the fringe case as we only create the instance during the first call of the method. Perhaps a performance hit on creation is tolerable. But even our main use-case, reads, will operate on the volatile member twice. Once to check existence, and again to return its value.

private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    MySingleton result = INSTANCE;
    if (result == null) {
        synchronized(MySingleton.class) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = createInstance();
            }
        }
    }

    return result;
}

Since the performance hit is due to operating directly on the volatile member, let's set a local variable to the value of the volatile and operate on the local variable instead. This will decrease the number of times we operate on the volatile, thereby reclaiming some of our lost performance. Note that we have to set our local variable again when we enter the synchronized block. This ensures it is up to date with any changes that occured while we were waiting for the lock.

I wrote an article about this recently. Deconstructing The Singleton . You can find more info on these examples and an example of the "holder" pattern there. There is also a real-world example showcasing the double-checked volatile approach. أتمنى أن يساعدك هذا.




أود أن أقول Enum singleton

Singleton يستخدم التعداد في Java بشكل عام لإعلان التعداد المفرد. قد يحتوي التعداد المفرد على متغير مثيل وطريقة مثيل. من أجل التبسيط ، لاحظ أيضًا أنه إذا كنت تستخدم أي طريقة مثيل من الحاجة إلى ضمان سلامة الخيط بهذه الطريقة على الإطلاق ، فإنها تؤثر على حالة الكائن.

من السهل جداً استخدام التعداد وليس له أي عيوب فيما يتعلق بالأشياء القابلة للتسلسل ، والتي يجب التحايل عليها بالطرق الأخرى.

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

يمكنك الوصول إليه بواسطة Singleton.INSTANCE ، أسهل بكثير من استدعاء الأسلوب getInstance() على Singleton.

1.12 مسلسل ثواب التعداد

يتم إجراء تسلسل ثوابت التعداد بشكل مختلف عن الكائنات القابلة للتسلسل أو القابلة للتخصيص. الشكل المتسلسل لثابت التعداد يتكون فقط من اسمه ؛ قيم المجال الثابت غير موجودة في النموذج. لإجراء تسلسل ثابت تعداد ، يكتب ObjectOutputStream القيمة التي تم إرجاعها بواسطة أسلوب اسم التعداد الثابت. لإلغاء تسلسل ثابت تعداد ، يقرأ ObjectInputStream الاسم الثابت من الدفق؛ ثم يتم الحصول على الثابت deserialized عن طريق استدعاء الأسلوب java.lang.Enum.valueOf ، تمرير نوع التعداد الثابت مع اسم ثابت المتلقي كوسيطة. مثل غيرها من الكائنات القابلة للتسلسل أو الخارجية ، يمكن أن تعمل ثوابت التعداد كأهداف للمراجع الخلفية التي تظهر لاحقًا في تدفق التسلسل.

لا يمكن تخصيص العملية التي يتم بها إجراء تسلسل ثوابت التعداد: يتم تجاهل أي writeObject و readResolve و readObject و readObjectNoData و writeReplace و readResolve الأساليب المحددة بواسطة أنواع التعداد أثناء التسلسل readResolve التسلسل. وبالمثل ، يتم أيضًا تجاهل أي serialPersistentFields أو serialVersionUID الحقل - جميع أنواع التعداد لديها serialVersionUID ثابت من 0L . توثيق الحقول القابلة للتسلسل والبيانات لأنواع التعداد غير ضروري ، لأنه لا يوجد اختلاف في نوع البيانات المرسلة.

مقتبس من مستندات Oracle

مشكلة أخرى مع Singletons التقليدية هي أنه بمجرد تطبيق واجهة Serializable ، فإنها لم تعد تظل Singleton لأن الطريقة readObject() دائماً بإرجاع نسخة جديدة مثل مُنشئ في Java. يمكن تجنب ذلك باستخدام readResolve() المثيل الذي تم إنشاؤه حديثًا عن طريق استبدال singleton مثل أدناه

 // readResolve to prevent another instance of Singleton
 private Object readResolve(){
     return INSTANCE;
 }

يمكن أن يصبح هذا أكثر تعقيدًا إذا كانت فئة Singleton الخاصة بك تحتفظ بحالة ، حيث تحتاج إلى جعلها عابرة ، ولكن في Enum Singleton ، يتم ضمان التسلسل بواسطة JVM.

قراءة جيدة

  1. نمط سينجلتون
  2. التعداد ، Singletons و Deserialization
  3. قفل مزدوج التحقق والنمط Singleton



لا تنس أن Singleton هو فقط Singleton لـ Classloader الذي قام بتحميله. إذا كنت تستخدم لوادر متعددة (حاويات) كل COULD لها نسختها الخاصة من Singleton.




أستخدم إطار Spring لإدارة My singletons. إنها لا تفرض "الطبقة المفردة" للفئة (التي لا يمكنك فعلها على أي حال إذا كان هناك العديد من لوادر التحميل) ولكنها توفر طريقة سهلة لبناء وتكوين مصانع مختلفة لإنشاء أنواع مختلفة من الكائنات.




I still think after java 1.5, enum is the best available singleton implementation available as it also ensures that even in the multi threaded environments - only one instance is created.

public enum Singleton{ INSTANCE; }

and you are done !!!




إخلاء المسؤولية: لقد لخصت كل الإجابات الرائعة وكتبتها في كلماتي.

أثناء تنفيذ Singleton لدينا 2 خيارات
1. تحميل كسول
2. التحميل المبكر

التحميل البطيء يضيف عبئا قليلا (الكثير من الصدق) بحيث لا تستخدمه إلا عندما يكون لديك جسم كبير جدا أو كود بناء ثقيل ولديك أيضا طرق أو حقول ثابتة أخرى يمكن الوصول إليها قبل الحاجة إلى مثيل ، ثم عندئذ فقط تحتاج إلى استخدام التهيئة البطيئة.خيار الاختيار المبكر هو خيار جيد.

معظم طريقة بسيطة لتنفيذ Singleton هو

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }
}

كل شيء جيد باستثناء المفرد في وقت مبكر تحميلها. دعونا نحاول سوليتون تحميل كسول

class Foo {

    // Our now_null_but_going_to_be sole hero 
    private static Foo INSTANCE = null;

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT  
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        // Creating only  when required.
        if (INSTANCE == null) {
            INSTANCE = new Foo();
        }
        return INSTANCE;
    }
}

حتى الآن جيد جدا لكن بطلنا لن ينجو بينما يقاتل بمفرده مع خيوط شريرة متعددة يريد الكثير من البطل. لذلك يتيح حمايتها من خيوط متعددة الشر

class Foo {

    private static Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        // No more tension of threads
        synchronized (Foo.class) {
            if (INSTANCE == null) {
                INSTANCE = new Foo();
            }
        }
        return INSTANCE;
    }
}

لكنها ليست كافية لحماية البطل ، حقا !!! هذا هو أفضل ما يمكن / يجب علينا فعله لمساعدة بطلنا

class Foo {

    // Pay attention to volatile
    private static volatile Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        if (INSTANCE == null) { // Check 1
            synchronized (Foo.class) {
                if (INSTANCE == null) { // Check 2
                    INSTANCE = new Foo();
                }
            }
        }
        return INSTANCE;
    }
}

وهذا ما يطلق عليه "لغة القفل ذات التحقق المزدوج". من السهل أن تنسى البيان المتقلب ويصعب فهم سبب ضرورة ذلك.
لمزيد من التفاصيل: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

الآن نحن على يقين حول موضوع الشر ولكن ماذا عن التسلسل القاسي؟ علينا أن نتأكد من أنه في حين أن de-serialiaztion لا يتم إنشاء أي كائن جديد

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object readResolve() {
        return INSTANCE;
    }
}

readResolve() الطريقة readResolve() التأكد من أن المثيل الوحيد سيتم إرجاعه ، حتى عندما يتم إجراء تسلسل للكائن في تشغيل سابق لبرنامجنا.

وأخيرًا ، أضفنا حماية كافية ضد الخيوط والتسلسلات ، لكن رمزنا يبدو ضخمًا وقبيحًا. دعونا نعطي بطلنا أكثر من ذلك

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    // Wrapped in a inner static class so that loaded only when required
    private static class FooLoader {

        // And no more fear of threads
        private static final Foo INSTANCE = new Foo();
    }

    // TODO add private shouting construcor

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    // Damn you serialization
    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

نعم هذا هو بطلنا نفسه جدا :)
منذ السطر private static final Foo INSTANCE = new Foo(); يتم تنفيذه فقط عندما يتم استخدام فئة FooLoader بالفعل ، وهذا يعتني FooLoader البطيئة ،

وهل يضمن الخيط الآمن.

لقد جئنا حتى الآن ، هنا أفضل طريقة لتحقيق كل ما قمنا به هو أفضل طريقة ممكنة

 public enum Foo {
       INSTANCE;
   }

التي سوف تعامل داخليا مثل

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

هذا هو أنه لا مزيد من الخوف من التسلسل والخيوط والرموز القبيحة. أيضا يتم تهيئة ENUMS المفرد بتكاسل .

هذا النهج مكافئ وظيفيا لنهج المجال العام ، إلا أنه أكثر إيجازا ، ويوفر آلية التسلسل مجانا ، ويوفر ضمان ironclad ضد تكرار متعددة ، حتى في مواجهة تسلسل متطورة أو هجمات التفكير. وبينما لم يتم بعد اعتماد هذا النهج على نطاق واسع ، فإن نمط التعداد ذي العنصر الواحد هو أفضل طريقة لتنفيذ المفرد.

- جوشوا بلوخ في "جاوة الفعالة"

الآن ربما تكون قد أدركت سبب اعتبار ENUMS كأفضل طريقة لتطبيق Singleton وشكرًا على صبرك :)
تحديثه على blog .




حقا النظر في السبب في أنك بحاجة إلى المفرد قبل كتابته. هناك جدل شبه ديني حول استخدامها ، والتي يمكنك أن تتعثر بسهولة أكثر إذا كنت google المفردات في جاوة.

أنا شخصياً أحاول أن أتجنب المفردات قدر المستطاع لأسباب كثيرة ، ومرة ​​أخرى يمكن العثور على معظمها من خلال مفردات googling. أشعر أنه في كثير من الأحيان يتم إساءة استخدام المفردات لأنها سهلة الفهم من قبل الجميع ، يتم استخدامها كآلية للحصول على البيانات "العالمية" في تصميم OO ويتم استخدامها لأنه من السهل التحايل على إدارة دورة حياة الكائن (أو حقا التفكير في كيف يمكنك القيام A من داخل B). انظر إلى أشياء مثل انقلاب التحكم (IoC) أو Dependency Injection (DI) لساحة وسطى لطيفة.

إذا كنت في حاجة حقاً إلى ذلك ، فستكون لدى wikipedia مثالاً جيدًا للتطبيق السليم لفرد واحد.




public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){
    if (INSTANCE != null)
        throw new IllegalStateException (“Already instantiated...”);
}

    public synchronized static Singleton getInstance() { 
    return INSTANCE;

    }

}

كما قمنا بإضافة الكلمة الأساسية Synchronized قبل getInstance ، قمنا بتجنب حالة السباق في الحالة عندما استدعاء اثنين مؤشرات ترابط getInstance في نفس الوقت.






Related