Singletons i Java

1. Introduktion

I den här snabba artikeln diskuterar vi de två mest populära sätten att implementera Singletons i vanlig Java.

2. Klassbaserad singleton

Det mest populära tillvägagångssättet är att implementera en Singleton genom att skapa en vanlig klass och se till att den har:

  • En privat konstruktör
  • Ett statiskt fält som innehåller sin enda instans
  • En statisk fabriksmetod för att erhålla förekomsten

Vi lägger också till en infoegenskap, endast för senare användning. Så vår implementering kommer att se ut så här:

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 }

Även om detta är ett vanligt tillvägagångssätt är det viktigt att notera att det kan vara problematiskt i multithreading-scenarier , vilket är den främsta anledningen till att använda Singletons.

Enkelt uttryckt kan det resultera i mer än en instans och bryta mönstrets kärnprincip. Även om det finns låsningslösningar på detta problem löser vårt nästa tillvägagångssätt dessa problem på rotnivå.

3. Enum Singleton

Framåt, låt oss inte diskutera ett annat intressant tillvägagångssätt - det är att använda uppräkningar:

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 }

Detta tillvägagångssätt har serialisering och trådsäkerhet garanterat av själva enum-implementeringen, vilket internt säkerställer att endast enstaka instans är tillgänglig, vilket korrigerar problemen som påpekas i klassbaserad implementering.

4. Användning

För att använda vår ClassSingleton behöver vi helt enkelt få instansen statiskt:

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är det gäller EnumSingleton kan vi använda den som alla andra 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. Vanliga fallgropar

Singleton är ett bedrägligt enkelt designmönster, och det finns få vanliga misstag som en programmerare kan begå när man skapar en singleton.

Vi skiljer två typer av problem med singletons:

  • existentiell (behöver vi en singleton?)
  • implementerande (implementerar vi det ordentligt?)

5.1. Existentiella frågor

Konceptuellt är en singleton en slags global variabel. Generellt vet vi att globala variabler bör undvikas - särskilt om deras tillstånd är mutabla.

Vi säger inte att vi aldrig ska använda singletoner. Vi säger dock att det kan finnas effektivare sätt att organisera vår kod.

Om en metods implementering beror på ett singleton-objekt, varför inte skicka det som en parameter? I det här fallet visar vi uttryckligen vad metoden beror på. Som en konsekvens kan vi lätt håna dessa beroenden (om nödvändigt) när vi utför testning.

Till exempel används singletons ofta för att omfatta applikationens konfigurationsdata (dvs. anslutning till förvaret). Om de används som globala objekt blir det svårt att välja konfiguration för testmiljön.

Därför blir produktionsdatabasen bortskämd med testdata när vi kör testerna, vilket knappast är acceptabelt.

Om vi ​​behöver en singleton kan vi överväga möjligheten att delegera dess instansiering till en annan klass - en slags fabrik - som bör ta hand om att se till att det bara finns en instans av singleton i spel.

5.2. Implementeringsfrågor

Även om singletonerna verkar ganska enkla, kan deras implementeringar drabbas av olika problem. Allt resulterar i det faktum att vi kan få mer än bara en instans av klassen.

Synkronisering

Implementeringen med en privat konstruktör som vi presenterade ovan är inte trådsäker: den fungerar bra i en enkeltrådad miljö, men i en tråd med flera trådar bör vi använda synkroniseringstekniken för att garantera operationens atomicitet:

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

Notera nyckelordet synkroniserat i metoddeklarationen. Metodens kropp har flera operationer (jämförelse, instantiering och retur).

I avsaknad av synkronisering finns det en möjlighet att två trådar flätar in sina körningar på ett sådant sätt att uttrycket INSTANCE == null utvärderas till sant för båda trådarna och som ett resultat skapas två instanser av ClassSingleton .

Synkronisering kan påverka prestandan avsevärt. Om den här koden åberopas ofta bör vi påskynda den med olika tekniker som lat initialisering eller dubbelkontrollerad låsning (tänk på att detta kanske inte fungerar som förväntat på grund av kompilatoroptimeringar). Vi kan se mer information i vår handledning ”Dubbelkontrollerad låsning med Singleton”.

Flera instanser

Det finns flera andra problem med singletons relaterade till själva JVM som kan få oss att hamna i flera instanser av en singleton. Dessa frågor är ganska subtila, och vi ger en kort beskrivning av var och en av dem:

  1. En singleton ska vara unik per JVM. Detta kan vara ett problem för distribuerade system eller system vars interna baseras på distribuerad teknik.
  2. Varje klasslastare kan ladda sin version av singleton.
  3. En singleton kan samlas i sopor när ingen har en referens till den. Det här problemet leder inte till förekomsten av flera singletoninstanser åt gången, men när det återskapas kan instansen skilja sig från den tidigare versionen.

6. Sammanfattning

I denna snabba handledning fokuserade vi på hur man implementerar Singleton-mönstret med endast Java-kärnan, och hur man ser till att det är konsekvent och hur man använder dessa implementeringar.

Den fullständiga implementeringen av dessa exempel finns på GitHub.