Singletoner i Java

1. Introduktion

I denne hurtige artikel diskuterer vi de to mest populære måder at implementere Singletons på almindelig Java.

2. Klassebaseret singleton

Den mest populære tilgang er at implementere en Singleton ved at oprette en almindelig klasse og sørge for, at den har:

  • En privat konstruktør
  • Et statisk felt, der indeholder den eneste forekomst
  • En statisk fabriksmetode til opnåelse af forekomsten

Vi tilføjer også en info-ejendom til kun senere brug. Så vores implementering vil se sådan ud:

offentlig endelig klasse ClassSingleton {privat statisk ClassSingleton INSTANCE; private String info = "Initial info class"; privat ClassSingleton () {} offentlig statisk ClassSingleton getInstance () {hvis (INSTANCE == null) {INSTANCE = ny ClassSingleton (); } returner INSTANCE; } // getters og setters}

Selvom dette er en almindelig tilgang, er det vigtigt at bemærke, at det kan være problematisk i multithreading-scenarier, hvilket er hovedårsagen til brug af Singletons.

Kort sagt, det kan resultere i mere end en instans, der bryder mønsterets kerneprincip. Selvom der er låsningsløsninger på dette problem, løser vores næste tilgang disse problemer på rodniveau.

3. Enum Singleton

Lad os ikke diskutere en anden interessant tilgang - som er at bruge optællinger:

offentlig enum EnumSingleton {INSTANCE ("Initial class info"); private strenginfo; private EnumSingleton (strenginfo) {this.info = info; } offentlige EnumSingleton getInstance () {return INSTANCE; } // getters og setters}

Denne tilgang har serialisering og trådsikkerhed garanteret af selve enum-implementeringen, hvilket internt sikrer, at kun den enkelte forekomst er tilgængelig, hvilket korrigerer de problemer, der påpeges i den klassebaserede implementering.

4. Anvendelse

At bruge vores ClassSingleton, vi er simpelthen nødt til at få forekomsten statisk:

ClassSingleton classSingleton1 = ClassSingleton.getInstance (); System.out.println (classSingleton1.getInfo ()); // Indledende klasseinformation ClassSingleton classSingleton2 = ClassSingleton.getInstance (); classSingleton2.setInfo ("Ny klasseinformation"); System.out.println (classSingleton1.getInfo ()); // Ny klasseinformation System.out.println (classSingleton2.getInfo ()); // Ny klasseinfo

hvad angår EnumSingleton, vi kan bruge det som enhver anden Java Enum:

EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance (); System.out.println (enumSingleton1.getInfo ()); // Indledende enum info EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance (); enumSingleton2.setInfo ("Ny enum-info"); System.out.println (enumSingleton1.getInfo ()); // Ny info om enum System.out.println (enumSingleton2.getInfo ()); // Ny info om enum

5. Almindelige faldgruber

Singleton er et vildledende simpelt designmønster, og der er få almindelige fejl, som en programmør kan begå, når de opretter en singleton.

Vi skelner mellem to typer problemer med singletoner:

  • eksistentiel (har vi brug for en singleton?)
  • implementeringsmæssigt (implementerer vi det ordentligt?)

5.1. Eksistentielle problemer

Konceptuelt er en singleton en slags global variabel. Generelt ved vi, at globale variabler bør undgås - især hvis deres tilstande er mutable.

Vi siger ikke, at vi aldrig skal bruge singletoner. Vi siger dog, at der kan være mere effektive måder at organisere vores kode på.

Hvis implementeringen af ​​en metode afhænger af et enkelt objekt, hvorfor ikke sende det som en parameter? I dette tilfælde viser vi eksplicit, hvad metoden afhænger af. Som en konsekvens kan vi let spotte disse afhængigheder (om nødvendigt), når vi udfører test.

F.eks. Bruges singletoner ofte til at omfatte applikationens konfigurationsdata (dvs. forbindelse til lageret). Hvis de bruges som globale objekter, bliver det vanskeligt at vælge konfigurationen til testmiljøet.

Derfor, når vi kører testene, bliver produktionsdatabasen forkælet med testdataene, hvilket næppe er acceptabelt.

Hvis vi har brug for en singleton, kan vi overveje muligheden for at delegere dens instantiering til en anden klasse - en slags fabrik - der skal sørge for at sikre, at der kun er en forekomst af singleton i spil.

5.2. Implementeringsspørgsmål

Selvom singletonerne virker ret enkle, kan deres implementeringer lide af forskellige problemer. Alle resulterer i, at vi måske ender med at have mere end kun en forekomst af klassen.

Synkronisering

Implementeringen med en privat konstruktør, som vi præsenterede ovenfor, er ikke trådsikker: den fungerer godt i et enkelt trådmiljø, men i en multitrådet skal vi bruge synkroniseringsteknikken til at garantere atomiciteten i operationen:

offentlig synkroniseret statisk ClassSingleton getInstance () {hvis (INSTANCE == null) {INSTANCE = ny ClassSingleton (); } returner INSTANCE; }

Bemærk nøgleordet synkroniseret i metodedeklarationen. Metodens krop har flere operationer (sammenligning, instantiering og retur).

I mangel af synkronisering er der en mulighed for, at to tråde sammenfletter deres henrettelser på en sådan måde, at udtrykket INSTANCE == null evaluerer til rigtigt for begge tråde og som følge heraf to forekomster af ClassSingleton få oprettet.

Synkronisering kan påvirke ydeevnen markant. Hvis denne kode ofte påberåbes, skal vi fremskynde den ved hjælp af forskellige teknikker som f.eks doven initialisering eller dobbeltkontrolleret låsning (vær opmærksom på, at dette muligvis ikke fungerer som forventet på grund af optimering af compiler). Vi kan se flere detaljer i vores vejledning “Dobbeltkontrolleret låsning med Singleton”.

Flere forekomster

Der er flere andre problemer med singletonerne relateret til JVM selv, der kan få os til at ende med flere forekomster af en singleton. Disse problemer er ret subtile, og vi giver en kort beskrivelse af hver af dem:

  1. En singleton formodes at være unik pr. JVM. Dette kan være et problem for distribuerede systemer eller systemer, hvis interne er baseret på distribuerede teknologier.
  2. Hver klasselæsser indlæser muligvis sin version af singleton.
  3. En singleton bliver muligvis indsamlet skrald, når ingen har en henvisning til det. Dette problem fører ikke til tilstedeværelsen af ​​flere singleton-forekomster ad gangen, men når det genskabes, kan forekomsten afvige fra den tidligere version.

6. Konklusion

I denne hurtige vejledning fokuserede vi på, hvordan man implementerer Singleton-mønsteret kun ved hjælp af Java-kernen, og hvordan man kan sikre, at det er konsekvent, og hvordan man bruger disse implementeringer.

Den fulde implementering af disse eksempler kan findes på GitHub.