Dobbeltkontrolleret låsning med Singleton

1. Introduktion

I denne vejledning taler vi om det dobbeltkontrollerede låsemønster. Dette mønster reducerer antallet af låseopkøb ved blot at kontrollere låsningstilstanden på forhånd. Som et resultat af dette er der normalt et præstationsforøg.

Lad os se nærmere på, hvordan det fungerer.

2. Implementering

Lad os begynde med at overveje en simpel singleton med drakonisk synkronisering:

offentlig klasse DraconianSingleton {privat statisk DraconianSingleton-forekomst; offentlig statisk synkroniseret DraconianSingleton getInstance () {if (instans == null) {instans = ny DraconianSingleton (); } returnere instans; } // privat konstruktør og andre metoder ...}

På trods af at denne klasse er trådsikker, kan vi se, at der er en klar præstationsulempe: hver gang vi ønsker at få forekomsten af ​​vores singleton, er vi nødt til at erhverve en potentielt unødvendig lås.

For at ordne det, vi kunne i stedet starte med at kontrollere, om vi i første omgang har brug for at oprette objektet, og kun i så fald ville vi erhverve låsen.

Når vi går videre, vil vi udføre den samme kontrol igen, så snart vi går ind i den synkroniserede blok for at holde operationen atomisk:

offentlig klasse DclSingleton {privat statisk flygtig DclSingleton-forekomst; offentlig statisk DclSingleton getInstance () {hvis (forekomst == null) {synkroniseret (DclSingleton. klasse) {hvis (forekomst == null) {forekomst = ny DclSingleton (); }}} returner instans; } // privat konstruktør og andre metoder ...}

En ting at huske på med dette mønster er, at marken skal være flygtige for at forhindre problemer med usammenhængende cache. Faktisk tillader Java-hukommelsesmodellen offentliggørelse af delvist initialiserede objekter, og dette kan igen føre til subtile bugs.

3. Alternativer

Selvom den dobbeltkontrolerede låsning potentielt kan fremskynde tingene, har den mindst to problemer:

  • da det kræver flygtige nøgleord fungerer korrekt, det er ikke kompatibelt med Java 1.4 og lavere versioner
  • det er ret detaljeret, og det gør koden vanskelig at læse

Af disse grunde, lad os se på nogle andre muligheder uden disse fejl. Alle følgende metoder delegerer synkroniseringsopgaven til JVM.

3.1. Tidlig initialisering

Den nemmeste måde at opnå trådsikkerhed er at integrere oprettelsen af ​​objektet eller bruge en tilsvarende statisk blok. Dette udnytter det faktum, at statiske felter og blokke initialiseres efter hinanden (Java Language Specification 12.4.2):

offentlig klasse EarlyInitSingleton {privat statisk endelig EarlyInitSingleton INSTANCE = ny EarlyInitSingleton (); offentlig statisk EarlyInitSingleton getInstance () {return INSTANCE; } // privat konstruktør og andre metoder ...}

3.2. Initialisering efter behov

Da vi ved fra Java Language Specification-referencen i det foregående afsnit, ved, at en klasseinitialisering finder sted første gang vi bruger en af ​​dens metoder eller felter, kan vi desuden bruge en indlejret statisk klasse til at implementere doven initialisering:

offentlig klasse InitOnDemandSingleton {privat statisk klasse InstanceHolder {privat statisk endelig InitOnDemandSingleton INSTANCE = ny InitOnDemandSingleton (); } offentlig statisk InitOnDemandSingleton getInstance () {return InstanceHolder.INSTANCE; } // privat konstruktør og andre metoder ...}

I dette tilfælde er InstanceHolder klasse tildeler feltet første gang vi får adgang til det ved at påberåbe sig getInstance.

3.3. Enum Singleton

Den sidste løsning kommer fra Effektiv Java bog (Item 3) af Joshua Block og bruger en enum i stedet for en klasse. I skrivende stund betragtes dette som den mest kortfattede og sikre måde at skrive en singleton på:

offentlig enum EnumSingleton {INSTANCE; // andre metoder ...}

4. Konklusion

For at opsummere gennemgik denne hurtige artikel det dobbeltkontrollerede låsemønster, dets grænser og nogle alternativer.

I praksis gør den overdrevne bredhed og manglen på bagudkompatibilitet dette mønster fejlbehæftet, og derfor bør vi undgå det. I stedet skal vi bruge et alternativ, der giver JVM mulighed for at synkronisere.

Som altid er koden for alle eksempler tilgængelig på GitHub.