Java Singleton ve Senkronizasyon


117

Lütfen Singleton ve Multithreading ile ilgili sorularımı netleştirin:

  • Singleton'u çok iş parçacıklı bir ortamda Java'da uygulamanın en iyi yolu nedir?
  • Birden çok iş parçacığı getInstance() aynı anda yönteme erişmeye çalıştığında ne olur ?
  • Singleton yapabilir miyiz getInstance() synchronized?
  • Singleton sınıflarını kullanırken senkronizasyon gerçekten gerekli mi?

Yanıtlar:


211

Evet gerekli. Geç başlatma ile iş parçacığı güvenliğini sağlamak için kullanabileceğiniz birkaç yöntem vardır:

Acımasız senkronizasyon:

private static YourObject instance;

public static synchronized YourObject getInstance() {
    if (instance == null) {
        instance = new YourObject();
    }
    return instance;
}

Bu çözüm, gerçekte sadece ilk birkaçının olması gerektiğinde her iş parçacığının senkronize edilmesini gerektirir.

Senkronizasyonu iki kez kontrol edin :

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

Bu çözüm, yalnızca singletonunuzu almaya çalışan ilk birkaç iş parçacığının kilidi edinme sürecinden geçmesini sağlar.

Talep Üzerine Başlatma :

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

Bu çözüm, iş parçacığı güvenliğini sağlamak için Java bellek modelinin sınıf başlatma garantilerinden yararlanır. Her sınıf yalnızca bir kez yüklenebilir ve yalnızca ihtiyaç duyulduğunda yüklenecektir. Bu, ilk seferin getInstanceçağrılacağı, InstanceHolderyükleneceği ve instanceoluşturulacağı anlamına gelir ve bu, ClassLoaders tarafından kontrol edildiğinden , ek senkronizasyon gerekmez.


23
Uyarı - iki kez kontrol edilen senkronizasyona dikkat edin. Bellek modeliyle ilgili "sorunlar" nedeniyle Java 5 JVM öncesi ile düzgün çalışmıyor.
Stephen C

3
-1 Draconian synchronizationve Double check synchronizationgetInstance () - yöntem statik olmalıdır!
Grim

2
@PeterRader Onlar yok gerek olmak static, ancak bunlar olsaydı daha mantıklı olabilir. İstendiği gibi değiştirildi.
Jeffrey

4
İki kez kontrol edilmiş kilitleme uygulamanızın çalışacağı garanti edilmez. Aslında çift kontrol kilitleme için alıntı yaptığınız makalede açıklanmıştır. :) Burada, 1.5 ve üstü için düzgün çalışan uçucu kullanan bir örnek var (çift kontrol edilmiş kilitleme, 1.5'in altında düz bir şekilde kırılmıştır). Makalede de belirtilen talep sahibine ilişkin başlatma, cevabınızda muhtemelen daha basit bir çözüm olacaktır.
sıkışmış

2
@MediumOne AFAIK, rdoğruluk için gerekli değildir. Yerel bir değişkene erişmekten çok daha pahalı olduğu için, geçici alana erişimden kaçınmak için yapılan bir optimizasyondur.
Jeffrey

69

Bu desen, açık senkronizasyon olmadan örneğin iş parçacığı açısından güvenli bir tembel başlatması yapar !

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}

Çalışır çünkü sizin için tüm senkronizasyonu ücretsiz olarak yapmak için sınıf yükleyiciyi kullanır: Sınıfa MySingleton.Loaderilk olarak getInstance()yöntemin içinden erişilir , böylece Loadersınıf getInstance()ilk kez çağrıldığında yüklenir . Ayrıca, sınıf yükleyici, sınıfa erişmeden önce tüm statik başlatmanın tamamlandığını garanti eder - bu size iş parçacığı güvenliği sağlar.

Sihir gibi.

Aslında Jhurtado'nun enum kalıbına çok benziyor, ancak enum kalıbını enum kavramının kötüye kullanımı olarak görüyorum (işe yarasa da)


11
Senkronizasyon hala mevcut, sadece programcı yerine JVM tarafından uygulanıyor.
Jeffrey

@Jeffrey Tabii ki haklısın - hepsini yazıyordum (düzenlemelere bakın)
Bohemian

2
Bunun JVM için bir fark yaratmadığını anlıyorum, sadece kendi kendini belgeleyen kod söz konusu olduğunda benim için bir fark yarattığını söylüyorum. Java'da daha önce (veya enum) "son" anahtar kelime olmadan tüm büyük harfleri hiç görmedim, biraz bilişsel uyumsuzluk yaşadım. Java'yı tam zamanlı programlayan biri için, muhtemelen bir fark yaratmaz, ancak dilleri ileri geri atlarsanız, açık olmak yardımcı olur. Yeni başlayanlar için aynen. Yine de bu stile oldukça çabuk adapte olabileceğine eminim; büyük olasılıkla tümü büyük harf yeterlidir. Seçmeyi kastetme, gönderini beğendim.
Yakut

1
Mükemmel cevap, bunun bir kısmını anlamadım. "Ayrıca, sınıf yükleyici, sınıfa erişmeden önce tüm statik başlatmanın tamamlandığını garanti eder - size iş parçacığı güvenliği sağlayan budur." , iplik güvenliğine nasıl yardımcı olur, bu konuda biraz kafam karıştı.
gaurav jain

2
@ wz366 aslında, gerekli olmasa da, stil nedenlerinden dolayı (başka hiçbir kod ona erişemediği için etkili bir şekilde nihai olduğundan) finaleklenmesi gerektiğini kabul ediyorum . Bitti.
Bohemian

21

Java'da çok iş parçacıklı bir ortamda çalışıyorsanız ve tüm bu iş parçacıklarının bir sınıfın tek bir örneğine eriştiğini garanti etmeniz gerekiyorsa, bir Enum kullanabilirsiniz. Bu, serileştirmeyi yönetmenize yardımcı olma ek avantajına sahip olacaktır.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

ve sonra iş parçacıklarınızın örneğinizi şu şekilde kullanmasını sağlayın:

Singleton.SINGLE.myMethod();

8

Evet, getInstance()senkronize etmeniz gerekiyor . Değilse, sınıfın birden çok örneğinin yapılabileceği bir durum ortaya çıkabilir.

getInstance()Aynı anda arayan iki iş parçacığınızın olduğu durumu düşünün . Şimdi T1'in instance == nullkontrolü geçtiğini ve sonra T2'nin çalıştığını hayal edin. Bu noktada örnek oluşturulmaz veya ayarlanmaz, bu nedenle T2 kontrolü geçecek ve örneği oluşturacaktır. Şimdi uygulamanın T1'e geri döndüğünü hayal edin. Şimdi tekli oluşturuldu, ancak T1 zaten kontrolü yaptı! Nesneyi tekrar yapmaya devam edecek! getInstance()Senkronize yapmak bu sorunu önler.

Tekilleri iş parçacığı açısından güvenli hale getirmenin birkaç yolu vardır, ancak getInstance()senkronize hale getirmek muhtemelen en basitidir.


Tüm yöntem senkronizasyonu yapmak yerine nesne oluşturma kodunu Senkronize bloğa yerleştirerek yardımcı olur mu?
RickDavis

@RaoG Hayır . Senkronizasyon bloğunda hem kontrolü hem de yaratmayı istiyorsunuz . Bu iki işlemin kesintisiz olarak birlikte gerçekleşmesine ihtiyacınız var yoksa yukarıda anlattığım durum olabilir.
Oleksi

7

Enum singleton

İş parçacığı açısından güvenli olan bir Singleton uygulamanın en basit yolu bir Enum kullanmaktır

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

Bu kod, Java 1.5'te Enum'un piyasaya sürülmesinden bu yana çalışıyor

Çift kontrollü kilitleme

Çok iş parçacıklı bir ortamda (Java 1.5'ten başlayarak) çalışan "klasik" bir singleton kodlamak istiyorsanız, bunu kullanmalısınız.

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 ;
  }
}

Bu, 1.5'ten önce iş parçacığı için güvenli değildir çünkü volatile anahtar kelimenin gerçeklemesi farklıdır.

Erken yükleme Singleton (Java 1.5'ten önce bile çalışır)

Bu uygulama, sınıf yüklendiğinde singletonu başlatır ve iş parçacığı güvenliği sağlar.

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");
  }

}

2

Örneği sınıf yüklemesinde somutlaştırmak ve iş parçacığı eşitleme sorunlarını önlemek için statik kod bloğu da kullanabilirsiniz.

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}

@Vimsha Birkaç başka şey. 1. instanceFinal yapmalısınız 2. getInstance()Statik yapmalısınız .
John Vint

Singletonda bir iplik oluşturmak isteseniz ne yapardınız.
Arun George

@ arun-george bir iş parçacığı havuzu, gerekirse tek bir iş parçacığı havuzu kullanın ve iş parçacığınızın hangi hata olursa olsun asla ölmemesini sağlamak istiyorsanız bir süre (true) -try-catch-atılabilir ile çevreleyin?
tgkprog

0

Singleton'u çok iş parçacıklı bir ortamda Java'da uygulamanın en iyi yolu nedir?

Singleton'ı uygulamanın en iyi yolu için bu gönderiye bakın.

Java'da bir tekli model uygulamanın etkili bir yolu nedir?

Birden çok iş parçacığı aynı anda getInstance () yöntemine erişmeye çalıştığında ne olur?

Yöntemi uygulama şeklinize bağlıdır. Uçucu değişken olmadan çift kilitleme kullanırsanız, kısmen oluşturulmuş Singleton nesnesi elde edebilirsiniz.

Daha fazla ayrıntı için bu soruya bakın:

Bu çift kontrollü kilitleme örneğinde neden uçucu kullanılmaktadır?

Singleton'ın getInstance () işlevini senkronize edebilir miyiz?

Singleton sınıflarını kullanırken senkronizasyon gerçekten gerekli mi?

Singleton'ı aşağıdaki şekillerde uygularsanız gerekli değildir

  1. statik somutlaştırma
  2. Sıralama
  3. İstek üzerine başlatma ile LazyInitalaization_holder_idiom

Daha fazla ayrıntı için bu soruya bakın

Java Singleton Design Pattern: Sorular


0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Kaynak: Etkili Java -> Öğe 2

Sınıfın her zaman tekil kalacağından eminseniz, onu kullanmanızı önerir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.