Singletoni în Java

1. Introducere

În acest articol rapid, vom discuta despre cele mai populare două modalități de implementare a Singleton în Java simplă.

2. Singleton bazat pe clase

Cea mai populară abordare este de a implementa un Singleton prin crearea unei clase obișnuite și asigurându-vă că are:

  • Un constructor privat
  • Un câmp static care conține singura sa instanță
  • O metodă de fabrică statică pentru obținerea instanței

Vom adăuga, de asemenea, o proprietate de informații, numai pentru utilizare ulterioară. Deci, implementarea noastră va arăta astfel:

public final class ClassSingleton { private static ClassSingleton INSTANCE; private String info = "Initial info class"; private ClassSingleton() { } public static ClassSingleton getInstance() { if(INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; } // getters and setters }

Deși aceasta este o abordare obișnuită, este important să rețineți că poate fi problematică în scenarii multithreading , care este principalul motiv pentru utilizarea Singleton-urilor.

Mai simplu spus, poate avea ca rezultat mai multe instanțe, încălcând principiul de bază al modelului. Deși există soluții de blocare pentru această problemă, următoarea noastră abordare rezolvă aceste probleme la nivel rădăcină.

3. Enum Singleton

Mergând mai departe, să nu discutăm o altă abordare interesantă - aceea de a folosi enumerările:

public enum EnumSingleton { INSTANCE("Initial class info"); private String info; private EnumSingleton(String info) { this.info = info; } public EnumSingleton getInstance() { return INSTANCE; } // getters and setters }

Această abordare are serializarea și siguranța firelor garantate chiar de implementarea enum, care asigură intern că este disponibilă doar singura instanță, corectând problemele subliniate în implementarea bazată pe clase.

4. Utilizare

Pentru a utiliza ClassSingleton , trebuie pur și simplu să obținem instanța în mod static:

ClassSingleton classSingleton1 = ClassSingleton.getInstance(); System.out.println(classSingleton1.getInfo()); //Initial class info ClassSingleton classSingleton2 = ClassSingleton.getInstance(); classSingleton2.setInfo("New class info"); System.out.println(classSingleton1.getInfo()); //New class info System.out.println(classSingleton2.getInfo()); //New class info

În ceea ce privește EnumSingleton , îl putem folosi ca orice alt Java Enum:

EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance(); System.out.println(enumSingleton1.getInfo()); //Initial enum info EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance(); enumSingleton2.setInfo("New enum info"); System.out.println(enumSingleton1.getInfo()); // New enum info System.out.println(enumSingleton2.getInfo()); // New enum info

5. Capcane comune

Singletonul este un model de proiectare înșelător de simplu și există puține greșeli comune pe care un programator le-ar putea comite atunci când creează un singureton.

Distingem două tipuri de probleme cu singletonii:

  • existențial (avem nevoie de un singleton?)
  • implementare (o implementăm corect?)

5.1. Probleme existențiale

Conceptual, un singleton este un fel de variabilă globală. În general, știm că variabilele globale ar trebui evitate - mai ales dacă stările lor sunt mutabile.

Nu spunem că nu ar trebui să folosim singuri. Cu toate acestea, spunem că ar putea exista modalități mai eficiente de a ne organiza codul.

Dacă implementarea unei metode depinde de un obiect singleton, de ce să nu-l treceți ca parametru? În acest caz, arătăm în mod explicit de ce depinde metoda. În consecință, ne putem bate joc cu ușurință de aceste dependențe (dacă este necesar) atunci când efectuăm testarea.

De exemplu, singletonii sunt adesea folosiți pentru a cuprinde datele de configurare ale aplicației (adică, conexiunea la depozit). Dacă sunt utilizate ca obiecte globale, devine dificil să alegeți configurația pentru mediul de testare.

Prin urmare, atunci când executăm testele, baza de date de producție se strică cu datele testelor, ceea ce este greu de acceptat.

Dacă avem nevoie de un singleton, am putea lua în considerare posibilitatea delegării instanțierii acestuia către o altă clasă - un fel de fabrică - care ar trebui să aibă grijă să se asigure că există doar o singură instanță a singletonului în joc.

5.2. Probleme de implementare

Chiar dacă singletonii par destul de simpli, implementările lor pot suferi de diverse probleme. Toate rezultă în faptul că am putea ajunge să avem mai mult decât o singură instanță a clasei.

Sincronizare

Implementarea cu un constructor privat pe care l-am prezentat mai sus nu este sigură pentru fire: funcționează bine într-un mediu cu un singur fir, dar într-un mediu cu mai multe fire, ar trebui să folosim tehnica de sincronizare pentru a garanta atomicitatea operației:

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

Rețineți cuvântul cheie sincronizat în declarația metodei. Corpul metodei are mai multe operații (comparație, instanțiere și returnare).

În absența sincronizării, există posibilitatea ca două fire să intercaleze execuțiile în așa fel încât expresia INSTANCE == null să fie adevărată pentru ambele fire și, ca urmare, să se creeze două instanțe ale ClassSingleton .

Sincronizarea poate afecta semnificativ performanța. Dacă acest cod este invocat adesea, ar trebui să-l accelerăm folosind diverse tehnici, cum ar fi inițializarea leneșă sau blocarea verificată de două ori (rețineți că acest lucru ar putea să nu funcționeze conform așteptărilor din cauza optimizărilor compilatorului). Putem vedea mai multe detalii în tutorialul nostru „Blocare dublă cu Singleton“.

Instanțe multiple

Există câteva alte probleme legate de singletoni în legătură cu JVM în sine, care ne-ar putea determina să sfârșim cu mai multe instanțe ale unui singleton. Aceste probleme sunt destul de subtile și vom oferi o scurtă descriere pentru fiecare dintre ele:

  1. Un singleton ar trebui să fie unic pentru JVM. Aceasta ar putea fi o problemă pentru sistemele distribuite sau sistemele ale căror componente interne se bazează pe tehnologii distribuite.
  2. Fiecare încărcător de clasă ar putea încărca versiunea sa a singletonului.
  3. Un singleton ar putea fi colectat gunoi odată ce nimeni nu are o referință la acesta. Această problemă nu duce la prezența mai multor instanțe singulare la un moment dat, dar atunci când este recreată, instanța ar putea diferi de versiunea sa anterioară.

6. Concluzie

În acest tutorial rapid, ne-am concentrat asupra modului de implementare a modelului Singleton folosind numai Java de bază și cum să ne asigurăm că este consecvent și cum să folosim aceste implementări.

Implementarea completă a acestor exemple poate fi găsită pe GitHub.