Java'da tek tonlu bir desen uygulamanın etkili bir yolu nedir?
Java'da tek tonlu bir desen uygulamanın etkili bir yolu nedir?
Yanıtlar:
Bir numaralandırma kullanın:
public enum Foo {
INSTANCE;
}
Joshua Bloch bu yaklaşımı Google I / O 2008'deki Etkili Java Reloaded konuşmasında açıkladı : video bağlantısı . Ayrıca sunumunun 30-32. Slaytlarına bakın ( effect_java_reloaded.pdf ):
Serileştirilebilir Singleton Uygulamanın Doğru Yolu
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
Düzenleme: Bir "Etkili Java" online kısmı diyor ki:
"Bu yaklaşım, kamusal alan yaklaşımına işlevsel olarak eşdeğerdir, ancak daha özlü olması, serileştirme makinelerini ücretsiz olarak sağlar ve karmaşık serileştirme veya yansıma saldırıları karşısında bile birden fazla somutlaştırmaya karşı zırhlı bir garanti sağlar. ancak geniş çapta benimsenmesi nedeniyle, tek elementli bir enum tipi, bir singletonu uygulamanın en iyi yoludur . "
Kullanıma bağlı olarak birkaç "doğru" cevap vardır.
Java5 yapmanın en iyi yolu bir numaralandırma kullanmaktır:
public enum Foo {
INSTANCE;
}
Java5 öncesi, en basit durum:
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");
}
}
Kodun üzerinden geçelim. İlk olarak, sınıfın final olmasını istiyorsunuz. Bu durumda, final
anahtar kelimeyi kullanıcıların nihai olduğunu bildirmek için kullandım . Ardından, kullanıcıların kendi Foo'larını oluşturmasını önlemek için yapıcıyı özel yapmanız gerekir. Yapıcıdan bir istisna atmak, kullanıcıların ikinci bir Foo oluşturmak için yansıma kullanmasını önler. Sonra private static final Foo
tek örneği tutmak için bir alan ve public static Foo getInstance()
döndürmek için bir yöntem oluşturun. Java belirtimi, kurucunun yalnızca sınıf ilk kullanıldığında çağrılmasını sağlar.
Çok büyük bir nesneniz veya ağır inşaat kodunuz varsa VE ayrıca bir örneğe ihtiyaç duyulmadan önce kullanılabilecek diğer erişilebilir statik yöntemlere veya alanlara sahip olduğunuzda, ancak ancak o zaman tembel başlatma kullanmanız gerekir.
Örneği private static class
yüklemek için a kullanabilirsiniz . Kod daha sonra şöyle görünecektir:
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;
}
}
Çizgi private static final Foo INSTANCE = new Foo();
yalnızca FooLoader sınıfı gerçekten kullanıldığında yürütüldüğünden, bu tembel örneklemeyi halleder ve iş parçacığı güvenli olması garanti edilir.
Nesnenizi serileştirmek istediğinizde, serileştirmenin bir kopya oluşturmayacağından emin olmanız gerekir.
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;
}
}
Yöntem readResolve()
, nesnenin programınızın önceki bir çalıştırmasında serileştirilmiş olsa bile, tek örneğin döndürülmesini sağlar.
Feragatname: Tüm harika cevapları özetledim ve sözlerime yazdım.
Singleton uygularken 2 seçeneğimiz var
1. Tembel yükleme
2. Erken yükleme
Tembel yükleme biraz ek yük (dürüst olmak gerekirse) ekler, bu yüzden sadece çok büyük bir nesneniz veya ağır inşaat kodunuz olduğunda kullanın ve ayrıca bir örneğe ihtiyaç duyulmadan önce kullanılabilecek diğer erişilebilir statik yöntemlere veya alanlara sahip olun. Aksi takdirde erken yüklemeyi seçmek iyi bir seçimdir.
Singleton'u uygulamanın en basit yolu
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;
}
}
Erken yüklü singleton hariç her şey iyidir. Tembel yüklü singleton'u deneyelim
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;
}
}
Şimdiye kadar çok iyi ama kahramanımız, kahramanımızın birçok örneğini isteyen çok sayıda kötü iş parçacığı ile tek başına savaşırken hayatta kalmayacak. Böylece onu kötü çok iş parçacığından koruyalım
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;
}
}
ama kahramanı korumak için yeterli değil, Gerçekten !!! Bu, kahramanımıza yardım etmek için yapabileceğimiz / yapmamız gereken en iyi şey
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;
}
}
Buna "Çift Denetimli Kilitleme deyimi" denir. Uçucu ifadeyi unutmak ve neden gerekli olduğunu anlamak zor.
Ayrıntılar için: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Şimdi kötü iplik konusunda eminiz ama acımasız serileştirmeye ne dersiniz? Seri ayırma işlemi sırasında bile yeni bir nesne oluşturulmadığından emin olmalıyız
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;
}
}
Yöntem readResolve()
, nesnenin programımızın bir önceki çalışmasında serileştirilmiş olsa bile, tek örneğin döndürülmesini sağlar.
Son olarak, iş parçacıklarına ve serileştirmeye karşı yeterli koruma ekledik ancak kodumuz hantal ve çirkin görünüyor. Kahramanımıza bir makyaj yapalım
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;
}
}
Evet bu aynı kahramanımız :)
Çizgi private static final Foo INSTANCE = new Foo();
sadece sınıf FooLoader
gerçekten kullanıldığında yürütüldüğünden , bu tembel örneklemeyi halleder,
ve ipliğin güvenli olacağı garanti edilir.
Ve şimdiye kadar geldik, yaptığımız her şeye ulaşmanın en iyi yolu mümkün olan en iyi yol
public enum Foo {
INSTANCE;
}
Hangi dahili olarak tedavi edilecek
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
Bu kadar! Artık serileştirme, iş parçacığı ve çirkin kod korkusu yok. Ayrıca ENUMS singletonu tembel olarak başlatılır .
Bu yaklaşım, kamusal alan yaklaşımıyla işlevsel olarak eşdeğerdir, ancak daha özlü olması, serileştirme makinelerini ücretsiz olarak sağlaması ve sofistike serileştirme veya yansıma saldırıları karşısında bile çoklu somutlaşmaya karşı zırhlı bir garanti sağlar. Bu yaklaşım henüz geniş çapta benimsenmese de, tek elemanlı bir enum tipi bir singletonu uygulamanın en iyi yoludur.
-Joshua Bloch "Etkili Java"
Şimdi ÇETELELER Singleton ve sabrınız için teşekkür uygulamak için en iyi yol olarak kabul edilir neden fark olabilir :)
benim onu Güncelleme blogda .
serialVersionUID
arasında 0L
. Üçüncü sorun: Özelleştirme yok: Sınıflandırma ve serileştirme sırasında enum türleri tarafından tanımlanan herhangi bir sınıfa özgü writeObject, readObject, readObjectNoData, writeReplace ve readResolve yöntemleri yok sayılır.
Stu Thompson tarafından yayınlanan çözüm Java5.0 ve sonrasında geçerlidir. Ama bunu kullanmamayı tercih ederim çünkü hata eğilimli olduğunu düşünüyorum.
Uçucu ifadeyi unutmak ve neden gerekli olduğunu anlamak zor. Uçucu olmadan bu kod, çift kontrollu kilitleme antipatterninden dolayı artık güvenli değildir. Bununla ilgili daha fazla bilgiyi Uygulamada Java Eşzamanlılığı'nın 16.2.4. Paragrafına bakınız . Kısacası: Bu kalıp (Java5.0'dan önce veya geçici ifade olmadan) Bar nesnesine (hala) yanlış durumda olan bir başvuru döndürebilir.
Bu model performans optimizasyonu için icat edildi. Ama bu artık gerçek bir endişe değil. Aşağıdaki tembel başlatma kodunun hızlı ve daha da önemlisi okunması daha kolaydır.
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
getBar()
. (Ve getBar
"çok erken" olarak adlandırılırsa, singleonların nasıl uygulandığına bakılmaksızın aynı sorunla karşılaşırsınız.) Yukarıdaki kodun tembel sınıf yüklemesini burada görebilirsiniz: pastebin.com/iq2eayiR
Java 5 ve sonraki sürümlerde güvenli iş parçacığı:
class Foo {
private static volatile Bar bar = null;
public static Bar getBar() {
if (bar == null) {
synchronized(Foo.class) {
if (bar == null)
bar = new Bar();
}
}
return bar;
}
}
EDIT : volatile
Buradaki değiştiriciye dikkat edin . :) Önemli çünkü onsuz, diğer iş parçacıklarının JMM (Java Bellek Modeli) tarafından değerindeki değişiklikleri görmesi garanti edilmez. Senkronizasyon bununla ilgilenmez - sadece bu kod bloğuna erişimi serileştirir.
DÜZENLEME 2 : @Bno'nun cevabı, Bill Pugh (FindBugs) tarafından önerilen yaklaşımı detaylandırır ve daha iyi tartışılabilir. Git ve cevabını oyla.
Tembel başlatmayı unutun , çok sorunlu. Bu en basit çözüm:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
Gerçekten ihtiyacınız olduğundan emin olun. Buna karşı bazı argümanları görmek için "tekil anti-desen" için bir google yapın. Doğal olarak yanlış bir şey yok sanırım ama bu sadece bazı küresel kaynak / verileri ortaya çıkarmak için bir mekanizma yani bu en iyi yol olduğundan emin olun. Özellikle bağımlılık enjeksiyonunu özellikle birim testler kullanıyorsanız daha yararlı buldum çünkü DI, alay konusu kaynakları test amacıyla kullanmanıza izin veriyor.
DI'yi singleton kullanmaya alternatif olarak öneren cevaplardan bazılarına gizem duyuyorum; bunlar ilgisiz kavramlardır. DI'yi tekil veya tekil olmayan (örn. İplik başına) örnekleri enjekte etmek için kullanabilirsiniz. En azından Spring 2.x kullanıyorsanız bu doğrudur, diğer DI çerçeveleri için konuşamam.
Bu yüzden OP'ye cevabım (en önemsiz örnek kod hariç hepsi):
Bu yaklaşım, bir tektonun kullanılıp kolayca geri döndürülebilen bir uygulama ayrıntısı olduğu güzel bir ayrıştırılmış (ve dolayısıyla esnek ve test edilebilir) bir mimari sunar (kullandığınız tektonların iş parçacığı güvenli olması şartıyla).
TicketNumberer
hangisinin tek bir global örneği olması gerektiğini ve TicketIssuer
bir kod satırı içeren bir sınıf yazmak istediğinizi düşünün int ticketNumber = ticketNumberer.nextTicketNumber();
. Geleneksel singleton düşüncesinde, önceki kod satırı gibi bir şey olmalıdır TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
. DI düşüncesinde, sınıfın bir yapıcısı olurdu public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
.
main
yöntemi (veya minyonlarından biri) bağımlılığı yaratıp kurucuyu çağırırdı . Esasen, global bir değişkenin (veya global bir yöntemin) kullanımı, korkulan hizmet konumlandırıcı modelinin basit bir şeklidir ve tıpkı o modelin diğer tüm kullanımları gibi bağımlılık enjeksiyonu ile değiştirilebilir.
Yazmadan önce neden bir singletona ihtiyacınız olduğunu düşünün. Java'da Google singletonları yaparsanız, bunları kolayca kullanabileceğiniz yarı dini bir tartışma var.
Şahsen ben pek çok nedenden ötürü mümkün olduğunca sık tekillerden kaçınmaya çalışıyorum. Çoğu zaman singletonların istismar edildiğini hissediyorum, çünkü herkes tarafından anlaşılması kolay, bir OO tasarımına "küresel" veri almak için bir mekanizma olarak kullanılıyorlar ve kullanılıyorlar çünkü nesne yaşam döngüsü yönetimini atlatmak kolay (veya gerçekten A'yı B içinden nasıl yapabileceğinizi düşünmek). Güzel bir orta alan için Inversion of Control (IoC) veya Dependency Injection (DI) gibi şeylere bakın.
Gerçekten birine ihtiyacınız varsa, wikipedia tek bir uygun bir uygulama iyi bir örneğine sahiptir.
3 farklı yaklaşım aşağıdadır
1) Numaralandırma
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2) Çift kontrol Kilitleme / Tembel yükleme
/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
private static volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public static DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
3) statik fabrika yöntemi
/**
* Singleton pattern example with static factory method
*/
public class Singleton{
//initailzed during class loading
private static final Singleton INSTANCE = new Singleton();
//to prevent creating another instance of Singleton
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
Tek çerçevelerimi yönetmek için Spring Framework'ü kullanıyorum. Sınıfın "singleton-ness" i zorunlu kılmaz (eğer birden fazla sınıf yükleyici varsa gerçekten yapamazsınız), ancak farklı nesne türleri oluşturmak için farklı fabrikalar oluşturmak ve yapılandırmak için gerçekten kolay bir yol sağlar.
Versiyon 1:
public class MySingleton {
private static MySingleton instance = null;
private MySingleton() {}
public static synchronized MySingleton getInstance() {
if(instance == null) {
instance = new MySingleton();
}
return instance;
}
}
Tembel yükleme, tıkanma ile güvenli iplik, düşük performans synchronized
.
Versiyon 2:
public class MySingleton {
private MySingleton() {}
private static class MySingletonHolder {
public final static MySingleton instance = new MySingleton();
}
public static MySingleton getInstance() {
return MySingletonHolder.instance;
}
}
Tembel yükleme, tıkanmasız iplik güvenliği, yüksek performans.
Tembel yüklemeye ihtiyacınız yoksa, sadece deneyin
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() { return Singleton.INSTANCE; }
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Tembel yükleme yapmak ve Singleton'unuzun iş parçacığı için güvenli olmasını istiyorsanız, çift kontrol desenini deneyin
public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if(null == instance) {
synchronized(Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
protected Object clone() {
throw new CloneNotSupportedException();
}
}
Çift kontrol deseninin çalışması garanti edilmediğinden (derleyicilerle ilgili bazı sorunlar nedeniyle, bunun hakkında daha fazla bir şey bilmiyorum.), Ayrıca tüm getInstance yöntemini senkronize etmeyi veya tüm Singletons için bir kayıt defteri oluşturmayı deneyebilirsiniz.
volatile
Enum singleton diyebilirim
Java'da enum kullanan singleton genellikle enum singleton'u bildirmenin bir yoludur. Enum singleton örnek değişkeni ve örnek yöntemi içerebilir. Basitlik açısından, eğer herhangi bir örnek yöntemi kullanıyorsanız, bu yöntemin nesnenin durumunu etkiliyorsa, iplik güvenliğini sağlamanız gerektiğini unutmayın.
Bir enumun kullanımı çok kolaydır ve diğer şekillerde atlanması gereken serileştirilebilir nesnelerle ilgili herhangi bir dezavantajı yoktur.
/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
//perform operation here
}
}
Buna Singleton'da Singleton.INSTANCE
arama getInstance()
yönteminden çok daha kolay erişebilirsiniz .
1.12 Enum Sabitlerinin Serileştirilmesi
Enum sabitleri sıradan serileştirilebilir veya dışsallaştırılabilir nesnelerden farklı şekilde serileştirilir. Bir numaralandırma sabitinin serileştirilmiş formu yalnızca adından oluşur; sabitin alan değerleri formda mevcut değildir. Bir enum sabitini serileştirmek için enum sabitinin
ObjectOutputStream
name yöntemi tarafından döndürülen değeri yazar. Bir enum sabitinin serisiniObjectInputStream
kaldırmak için sabit adı akıştan okur; serileştirilmiş sabit, daha sonra sabit çağrınınjava.lang.Enum.valueOf
enum türünü, alınan sabit adla birlikte argümanlar olarak geçirerek yöntemin çağrılmasıyla elde edilir . Diğer serileştirilebilir veya dışsallaştırılabilir nesneler gibi, enum sabitleri de sonradan serileştirme akışında görünen geri referansların hedefleri olarak işlev görebilir.Enum sabitleri serileştirildiğini süreçtir özelleştirilemiyor: herhangi bir sınıf özgü
writeObject
,readObject
,readObjectNoData
,writeReplace
, vereadResolve
enum türleri tarafından tanımlanan yöntem seri ve seri kaldırma sırasında göz ardı edilir. Benzer şekilde, herhangi birserialPersistentFields
veyaserialVersionUID
alan bildirimi de yoksayılır - tüm numaralandırma türlerinin sabit birserialVersionUID
değeri vardır0L
. Gönderilen veri türünde herhangi bir değişiklik olmadığı için numaralandırma türleri için serileştirilebilir alanları ve verileri belgelemek gereksizdir.
Geleneksel Singletons'la ilgili bir başka sorun, Serializable
arabirimi uyguladıktan sonra artık tek başına kalmamasıdır çünkü readObject()
yöntem her zaman Java'da yapıcı gibi yeni bir örnek döndürür. Bu, readResolve()
aşağıdaki gibi singleton ile değiştirilerek yeni oluşturulan örnek kullanılarak ve atılarak önlenebilir
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
Bu, Singleton Sınıfınızın durumunu koruduğunda, geçici hale getirmeniz gerektiğinden daha da karmaşık hale gelebilir, ancak Enum Singleton'da, Serileştirme JVM tarafından garanti edilir.
İyi okuma
There are 4 ways to create a singleton in java.
1- eager initialization singleton
public class Test{
private static final Test test = new Test();
private Test(){}
public static Test getTest(){
return test;
}
}
2- lazy initialization singleton (thread safe)
public class Test {
private static volatile Test test;
private Test(){}
public static Test getTest() {
if(test == null) {
synchronized(Test.class) {
if(test == null){test = new Test();
}
}
}
return test;
}
3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)
public class Test {
private Test(){}
private static class TestHolder{
private static final Test test = new Test();
}
public static Test getInstance(){
return TestHolder.test;
}
}
4- enum singleton
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
Bu konuda oyuna biraz geç olabilir, ancak singletonu uygulamak konusunda çok fazla nüans var. Tutucu modeli birçok durumda kullanılamaz. Uçucu kullanırken IMO - yerel bir değişken de kullanmalısınız. En baştan başlayalım ve sorunu tekrarlayalım. Ne demek istediğimi göreceksin.
İlk deneme şöyle görünebilir:
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
Burada INSTANCE adlı özel bir statik üyeye ve getInstance () adlı bir genel statik yönteme sahip MySingleton sınıfımız var. GetInstance () yöntemi ilk çağrıldığında INSTANCE üyesi null olur. Akış daha sonra oluşturma koşuluna girecek ve MySingleton sınıfının yeni bir örneğini oluşturacaktır. GetInstance () öğesine yapılan sonraki çağrılar, INSTANCE değişkeninin önceden ayarlandığını görür ve bu nedenle başka bir MySingleton örneği oluşturmaz. Bu, tüm getInstance () çağrıları arasında paylaşılan yalnızca bir MySingleton örneği olmasını sağlar.
Ancak bu uygulamanın bir sorunu var. Çok iş parçacıklı uygulamalar, tek bir örneğin oluşturulması konusunda bir yarış durumuna sahip olacaktır. Birden çok yürütme iş parçacığı getInstance () yöntemini aynı anda (veya çevresinde) vurursa, her biri INSTANCE üyesini null olarak görür. Bu, her iş parçacığının yeni bir MySingleton örneği oluşturmasına ve ardından INSTANCE üyesini ayarlamasına neden olur.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
Burada getInstance () yöntemini senkronize etmek için yöntem imzasında senkronize edilmiş anahtar kelimeyi kullandık. Bu kesinlikle yarış durumumuzu düzeltir. Konular şimdi engellenecek ve yöntemi birer birer girecektir. Ama aynı zamanda bir performans sorunu yaratıyor. Bu uygulama yalnızca tek eşgörünümün oluşturulmasını senkronize etmekle kalmaz, okumalar dahil tüm çağrıları getInstance () öğesine senkronize eder. INSTANCE değerini döndürdüğü için okumaların senkronize edilmesi gerekmez. Okumalar çağrılarımızın çoğunu oluşturacağından (hatırlayın, örnekleme yalnızca ilk çağrıda gerçekleşir), tüm yöntemi senkronize ederek gereksiz bir performans isabetine gireceğiz.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
Burada senkronizasyonu yöntem imzasından MySingleton örneğinin oluşturulmasını saran senkronize bir bloğa taşıdık. Peki bu sorunumuzu çözüyor mu? Artık okumaları engellemiyoruz, ancak geriye doğru da bir adım attık. Birden çok iş parçacığı aynı anda veya çevresinde getInstance () yöntemine çarpar ve hepsi INSTANCE üyesini null olarak görür. Daha sonra, kilidi elde edip örneği oluşturacak olan senkronize bloğa çarparlar. Bu iş parçacığı bloktan çıktığında, diğer iş parçacıkları kilit için yarışacak ve her iş parçacığı tek tek bloktan düşecek ve sınıfımızın yeni bir örneğini oluşturacaktır. Yani başladığımız yere geri döndük.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Burada bloğun İÇİNDEN başka bir kontrol daha veriyoruz. INSTANCE üyesi önceden ayarlanmışsa, başlatmayı atlayacağız. Buna çift denetimli kilitleme denir.
Bu, çoklu örnekleme sorunumuzu çözer. Ama bir kez daha, çözümümüz başka bir zorluk getirdi. Diğer evreler INSTANCE üyesinin güncellendiğini "görmeyebilir". Bunun nedeni Java'nın bellek işlemlerini nasıl optimize ettiğidir. Konular değişkenlerin orijinal değerlerini ana bellekten CPU'nun önbelleğine kopyalar. Değerlerdeki değişiklikler daha sonra bu önbelleğe yazılır ve bu önbellekten okunur. Bu, performansı optimize etmek için tasarlanmış bir Java özelliğidir. Ancak bu, singleton uygulamamız için bir sorun yaratıyor. Farklı bir önbellek kullanarak farklı bir CPU veya çekirdek tarafından işlenen ikinci bir iş parçacığı, ilk işlem tarafından yapılan değişiklikleri görmez. Bu, ikinci iş parçacığının INSTANCE üyesini, singleton'umuzun yeni bir örneğini oluşturmaya zorlayacak şekilde görmesine neden olur.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Bunu, INSTANCE üyesinin beyanındaki geçici anahtar kelimeyi kullanarak çözüyoruz. Bu derleyiciye her zaman CPU önbelleğinden değil ana bellekten okumasını ve yazmasını sağlar.
Ancak bu basit değişikliğin bir bedeli vardır. CPU önbelleğini atladığımız için, geçici INSTANCE üyesinde her çalıştığımızda 4 kez yaptığımız bir performans isabeti alacağız. Varlığı (1 ve 2) iki kez kontrol eder, değeri (3) ayarlarız ve sonra değeri (4) döndürürüz. Bir örnek, yalnızca yöntemin ilk çağrısı sırasında örneği oluşturduğumuz için bu yolun saçak durumu olduğunu iddia edebilir. Belki de yaratılıştaki bir performans tolere edilebilir. Ancak, ana kullanım durumumuz bile, uçucu eleman üzerinde iki kez çalışacaktır. Bir kez varlığını kontrol etmek ve tekrar değerini döndürmek için.
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;
}
Performans isabeti doğrudan uçucu eleman üzerinde çalıştığından kaynaklandığı için, uçucunun değerine bir yerel değişken ayarlayalım ve bunun yerine yerel değişken üzerinde çalışalım. Bu, uçucu madde üzerinde kaç kez çalıştığımızı azaltacak ve böylece kayıp performansımızın bir kısmını geri kazanacaktır. Senkronize bloğa girdiğimizde yerel değişkenimizi tekrar ayarlamamız gerektiğini unutmayın. Bu, kilidi beklerken meydana gelen değişikliklerle güncel kalmasını sağlar.
Son zamanlarda bunun hakkında bir makale yazdım. Singleton'un Yapısından Çıkarılması . Bu örnekler hakkında daha fazla bilgi ve orada "tutucu" desen örneği bulabilirsiniz. Ayrıca, çift kontrol edilen uçucu yaklaşımı gösteren gerçek dünya örneği de vardır. Bu yardımcı olur umarım.
BearerToken instance
senin içinde makalesinde değildir static
? Ne oldu result.hasExpired()
?
class MySingleton
- belki de olmalı final
?
BearerToken
Örnek, BearerTokenFactory
belirli bir yetkilendirme sunucusuyla yapılandırılan - öğesinin parçası olduğu için statik değildir . Çok sayıda BearerTokenFactory
nesne olabilir - her birinin BearerToken
süresi doluncaya kadar dağıttığı kendi “önbelleğe alınmış” olması. Üzerindeki hasExpired()
yöntem, süresi dolmuş bir belirteci dağıtmadığından emin olmak BeraerToken
için fabrika get()
yönteminde çağrılır . Süresi dolmuşsa, yetkilendirme sunucusundan yeni bir kod istenir. Kod bloğunu izleyen paragraf bunu daha ayrıntılı olarak açıklamaktadır.
Basit bir uygulama şu şekildedir singleton
:
public class Singleton {
// It must be static and final to prevent later modification
private static final Singleton INSTANCE = new Singleton();
/** The constructor must be private to prevent external instantiation */
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return INSTANCE;
}
}
Uygun şekilde tembel oluşturmak için singleton
:
public class Singleton {
// The constructor must be private to prevent external instantiation
private Singleton(){}
/** The public static method allowing to get the instance */
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* The static inner class responsible for creating your instance only on demand,
* because the static fields of a class are only initialized when the class
* is explicitly called and a class initialization is synchronized such that only
* one thread can perform it, this rule is also applicable to inner static class
* So here INSTANCE will be created only when SingletonHolder.INSTANCE
* will be called
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
getInstance()
. Ama aslında sınıfınızda başka statik yöntemler yoksa Singleton
ve sadece getInstance()
gerçek bir fark olmadığını söylüyorsanız.
Bir sınıfın örnek değişkenini tembel olarak yüklemeniz gerekiyorsa, çift denetleme deyimine ihtiyacınız vardır. Statik bir değişken veya bir tektonu tembel olarak yüklemeniz gerekiyorsa , talep sahibi deyiminde başlatmaya ihtiyacınız vardır .
Ayrıca, singleton'un seriliazble olması gerekiyorsa, singleton nesnesinin değişmezliğini korumak için diğer tüm alanların geçici olması ve readResolve () yönteminin uygulanması gerekir. Aksi takdirde, nesnenin serisini her silme işlemi gerçekleştirildiğinde, nesnenin yeni bir örneği oluşturulur. ReadResolve () işlevi, readObject () tarafından okunan yeni nesnenin yerine geçer; bu, yeni nesneyi, ona başvuran bir değişken olmadığından çöp toplanmaya zorlar.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
Singleton nesnesi yapmanın çeşitli yolları:
Joshua Bloch'a göre - Enum en iyisi olurdu.
ayrıca çift çek kilitlemeyi de kullanabilirsiniz.
İç statik sınıf bile kullanılabilir.
Enum singleton
İş parçacığı açısından güvenli olan bir Singletonu 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 Enum tanıtımı beri çalışır
Çift kontrollü kilitleme
Çok iş parçacıklı bir ortamda (Java 1.5'ten başlayarak) çalışan bir “klasik” singletonu 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, uçucu anahtar kelimenin uygulanması farklı olduğundan 1.5'ten önce güvenli değildir.
Erken yükleme Singleton (Java 1.5'ten önce bile çalışır)
Bu uygulama, sınıf yüklendiğinde singleton'u 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");
}
}
Singletons'a karşı sıkça kullanılan bir diğer argüman, test edilebilirlik problemleridir. Singletons test amacıyla kolayca taklit edilemez. Bu bir sorun haline gelirse, aşağıdaki küçük değişiklikleri yapmak istiyorum:
public class SingletonImpl {
private static SingletonImpl instance;
public static SingletonImpl getInstance() {
if (instance == null) {
instance = new SingletonImpl();
}
return instance;
}
public static void setInstance(SingletonImpl impl) {
instance = impl;
}
public void a() {
System.out.println("Default Method");
}
}
Eklenen setInstance
yöntem, test sırasında singleton sınıfının mockup uygulamasını ayarlamaya izin verir:
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Bu aynı zamanda erken başlatma yaklaşımlarıyla da çalışır:
public class SingletonImpl {
private static final SingletonImpl instance = new SingletonImpl();
private static SingletonImpl alt;
public static void setInstance(SingletonImpl inst) {
alt = inst;
}
public static SingletonImpl getInstance() {
if (alt != null) {
return alt;
}
return instance;
}
public void a() {
System.out.println("Default Method");
}
}
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Bu, bu işlevselliği normal uygulamaya da maruz bırakmanın dezavantajına sahiptir. Bu kod üzerinde çalışan diğer geliştiriciler, belirli bir işlevi değiştirmek ve böylece tüm uygulama davranışını değiştirmek için ´setInstance´ yöntemini kullanmaya cazip gelebilir, bu nedenle bu yöntem, javadocunda en azından iyi bir uyarı içermelidir.
Yine de, mockup testi (gerektiğinde) olasılığı için, bu kod maruziyeti ödeme için kabul edilebilir bir fiyat olabilir.
en basit singleton sınıfı
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
Java 1.5'ten sonra hala enum mevcut en iyi singleton uygulaması olduğunu düşünüyorum çünkü aynı zamanda çok iş parçacıklı ortamlarda bile - sadece bir örnek oluşturulmasını sağlıyor.
public enum Singleton{
INSTANCE;
}
ve işiniz bitti !!!
Bu gönderiye bir göz atın.
Java'nın çekirdek kütüphanelerindeki GoF Tasarım Kalıplarına örnekler
En iyi yanıtın "Singleton" bölümünden,
Singleton (her seferinde aynı örneği (genellikle kendi başına) döndüren yaratıcı yöntemlerle tanınabilir)
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
Singleton örneğini Java yerel sınıflarından da öğrenebilirsiniz.
Şimdiye kadar gördüğüm en iyi singleton modeli Tedarikçi arayüzünü kullanıyor.
Aşağıya bakınız:
public class Singleton<T> implements Supplier<T> {
private boolean initialized;
private Supplier<T> singletonSupplier;
public Singleton(T singletonValue) {
this.singletonSupplier = () -> singletonValue;
}
public Singleton(Supplier<T> supplier) {
this.singletonSupplier = () -> {
// The initial supplier is temporary; it will be replaced after initialization
synchronized (supplier) {
if (!initialized) {
T singletonValue = supplier.get();
// Now that the singleton value has been initialized,
// replace the blocking supplier with a non-blocking supplier
singletonSupplier = () -> singletonValue;
initialized = true;
}
return singletonSupplier.get();
}
};
}
@Override
public T get() {
return singletonSupplier.get();
}
}
Bazen basit bir " static Foo foo = new Foo();
" yeterli olmaz. Sadece yapmak istediğiniz bazı temel veri eklemeyi düşünün.
Öte yandan, singleton değişkenini bu şekilde başlatan herhangi bir yöntemi senkronize etmeniz gerekir. Senkronizasyon bu kadar kötü değildir, ancak performans sorunlarına veya kilitlemeye neden olabilir (çok nadir durumlarda bu örneği kullanır.
public class Singleton {
private static Singleton instance = null;
static {
instance = new Singleton();
// do some of your instantiation stuff here
}
private Singleton() {
if(instance!=null) {
throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
}
}
public static getSingleton() {
return instance;
}
}
Şimdi ne olacak? Sınıf, sınıf yükleyici aracılığıyla yüklenir. Sınıf bir bayt dizisinden yorumlandıktan hemen sonra, VM statik {} - bloğunu yürütür . sırrın tamamı budur: Statik blok yalnızca bir kez çağrılır, verilen paketin verilen sınıfının (adının) bu bir sınıf yükleyici tarafından yüklenmesi.
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;
}
}
Senkronize anahtar sözcüğünü getInstance'dan önce eklediğimizden, iki iş parçacığının aynı anda getInstance'ı çağırması durumunda yarış koşulundan kaçındık.