Dubbel gecontroleerd vergrendelen met Singleton

1. Inleiding

In deze zelfstudie zullen we het hebben over het dubbel gecontroleerde ontwerppatroon voor vergrendeling. Dit patroon vermindert het aantal vergrendelingsacquisities door simpelweg de vergrendelingstoestand van tevoren te controleren. Als gevolg hiervan is er meestal een prestatieverbetering.

Laten we eens nader bekijken hoe het werkt.

2. Implementatie

Laten we om te beginnen eens kijken naar een simpele singleton met draconische synchronisatie:

openbare klasse DraconianSingleton {privé statische DraconianSingleton-instantie; openbare statische gesynchroniseerde DraconianSingleton getInstance () {if (instance == null) {instance = new DraconianSingleton (); } terugkeerinstantie; } // private constructor en andere methoden ...}

Ondanks dat deze klasse thread-safe is, kunnen we zien dat er een duidelijk prestatienadeel is: elke keer dat we de instantie van onze singleton willen krijgen, moeten we een mogelijk onnodige vergrendeling aanschaffen.

Om dat op te lossen, we zouden in plaats daarvan kunnen beginnen met te verifiëren of we het object in de eerste plaats moeten maken en alleen in dat geval zouden we het slot verkrijgen.

Als we verder gaan, willen we dezelfde controle opnieuw uitvoeren zodra we het gesynchroniseerde blok binnengaan, om de bewerking atomisch te houden:

openbare klasse DclSingleton {privé statische vluchtige DclSingleton-instantie; openbare statische DclSingleton getInstance () {if (instance == null) {gesynchroniseerd (DclSingleton .class) {if (instance == null) {instance = nieuwe DclSingleton (); }}} terugkeerinstantie; } // private constructor en andere methoden ...}

Een ding om in gedachten te houden met dit patroon is dat het veld moet zijn vluchtig om problemen met cache-incoherentie te voorkomen. In feite maakt het Java-geheugenmodel de publicatie van gedeeltelijk geïnitialiseerde objecten mogelijk, wat op zijn beurt kan leiden tot subtiele bugs.

3. Alternatieven

Hoewel de dubbel gecontroleerde vergrendeling de zaken mogelijk kan versnellen, heeft deze ten minste twee problemen:

  • omdat het de vluchtig trefwoord correct te laten werken, is het niet compatibel met Java 1.4 en lagere versies
  • het is nogal uitgebreid en het maakt de code moeilijk te lezen

Laten we om deze redenen eens kijken naar enkele andere opties zonder deze gebreken. Alle volgende methoden delegeren de synchronisatietaak naar de JVM.

3.1. Vroege initialisatie

De eenvoudigste manier om thread-veiligheid te bereiken, is door het maken van objecten inline te plaatsen of door een gelijkwaardig statisch blok te gebruiken. Dit maakt gebruik van het feit dat statische velden en blokken na elkaar worden geïnitialiseerd (Java-taalspecificatie 12.4.2):

openbare klasse EarlyInitSingleton {privé statische laatste EarlyInitSingleton INSTANCE = nieuwe EarlyInitSingleton (); openbare statische EarlyInitSingleton getInstance () {terugkeer INSTANCE; } // private constructor en andere methoden ...}

3.2. Initialisatie op aanvraag

Bovendien, aangezien we uit de Java-taalspecificatie-verwijzing in de vorige paragraaf weten dat een klasse-initialisatie plaatsvindt de eerste keer dat we een van zijn methoden of velden gebruiken, kunnen we een geneste statische klasse gebruiken om luie initialisatie te implementeren:

openbare klasse InitOnDemandSingleton {privé statische klasse InstanceHolder {privé statische finale InitOnDemandSingleton INSTANCE = nieuwe InitOnDemandSingleton (); } openbare statische InitOnDemandSingleton getInstance () {terug InstanceHolder.INSTANCE; } // private constructor en andere methoden ...}

In dit geval is het InstanceHolder class zal het veld toewijzen de eerste keer dat we het openen door aan te roepen getInstance.

3.3. Enum Singleton

De laatste oplossing komt van de Effectieve Java boek (item 3) door Joshua Block en gebruikt een opsomming inplaats van een klasse. Op het moment van schrijven wordt dit beschouwd als de meest beknopte en veilige manier om een ​​singleton te schrijven:

openbare opsomming EnumSingleton {INSTANCE; // andere methodes... }

4. Conclusie

Kortom, dit korte artikel ging door het dubbelgecontroleerde vergrendelingspatroon, de limieten en enkele alternatieven.

In de praktijk maken de buitensporige breedsprakigheid en het gebrek aan achterwaartse compatibiliteit dit patroonfoutgevoelig en daarom moeten we het vermijden. In plaats daarvan zouden we een alternatief moeten gebruiken waarmee de JVM de synchronisatie kan uitvoeren.

Zoals altijd is de code van alle voorbeelden beschikbaar op GitHub.