Kétszeresen ellenőrzött zár a Singletonnal

1. Bemutatkozás

Ebben az oktatóanyagban a kétszeresen ellenőrzött zárszerkezeti mintáról beszélünk. Ez a minta csökkenti a zárszerzések számát azáltal, hogy előzetesen egyszerűen ellenőrzi a reteszelés állapotát. Ennek eredményeként általában teljesítménynövekedés tapasztalható.

Vizsgáljuk meg mélyebben a működését.

2. Végrehajtás

Először is vegyünk egy egyszerű drakóniai szinkronizálást:

nyilvános osztály DraconianSingleton {privát statikus DraconianSingleton példány; nyilvános statikus szinkronizált DraconianSingleton getInstance () {if (instance == null) {instance = new DraconianSingleton (); } visszatérési példány; } // magánépítő és egyéb módszerek ...}

Annak ellenére, hogy ez az osztály szálbiztos, láthatjuk, hogy van egy egyértelmű teljesítmény-hátrány: minden egyes alkalommal, amikor meg akarjuk szerezni a szingulettünk példányát, be kell szereznünk egy potenciálisan felesleges zárat.

Ennek kijavítására, ehelyett ellenőrizhetjük, hogy eleve létre kell-e hoznunk az objektumot, és csak ebben az esetben szereznénk meg a zárat.

Tovább haladva szeretnénk újra elvégezni ugyanazt az ellenőrzést, amint belépünk a szinkronizált blokkba, annak érdekében, hogy a művelet atomi maradjon:

public class DclSingleton {private static volatile DclSingleton instance; public static DclSingleton getInstance () {if (instance == null) {synchronized (DclSingleton .class) {if (instance == null) {instance = new DclSingleton (); }}} visszatérési példány; } // magánépítő és egyéb módszerek ...}

Egy dolgot szem előtt kell tartani ezzel a mintával: a mezőnek meg kell lennie illó a gyorsítótár inkoherencia problémáinak megelőzése érdekében. Valójában a Java memóriamodell lehetővé teszi részben inicializált objektumok közzétételét, ez pedig finom hibákhoz vezethet.

3. Alternatívák

Annak ellenére, hogy a kétszeresen ellenőrzött reteszelés felgyorsíthatja a dolgokat, legalább két kérdése van:

  • mivel megköveteli a illó kulcsszó megfelelő működéséhez, nem kompatibilis a Java 1.4 és az alacsonyabb verziókkal
  • elég bőbeszédű és megnehezíti a kód olvasását

Ezen okokból nézzünk meg néhány további lehetőséget e hibák nélkül. Az alábbi módszerek mindegyike átruházza a szinkronizálási feladatot a JVM-re.

3.1. Korai inicializálás

A szálbiztonság elérésének legegyszerűbb módja az objektum létrehozásának beillesztése vagy ennek megfelelő statikus blokk használata. Ez kihasználja azt a tényt, hogy a statikus mezőket és blokkokat egymás után inicializálják (Java nyelvi specifikáció 12.4.2):

public class EarlyInitSingleton {private static final EarlyInitSingleton INSTANCE = új EarlyInitSingleton (); public static EarlyInitSingleton getInstance () {return INSTANCE; } // magánépítő és egyéb módszerek ...}

3.2. Igény szerinti inicializálás

Ezenkívül, mivel az előző bekezdés Java Nyelvi Specifikáció hivatkozásából tudjuk, hogy egy osztály inicializálása akkor fordul elő, amikor először használjuk valamelyik módszerét vagy mezőjét, beágyazott statikus osztályt használhatunk a lusta inicializálás megvalósításához:

public class InitOnDemandSingleton {private static class instanceHolder {private static final InitOnDemandSingleton INSTANCE = új InitOnDemandSingleton (); } public static InitOnDemandSingleton getInstance () {return instanceHolder.INSTANCE; } // magánépítő és egyéb módszerek ...}

Ebben az esetben a Példánytulajdonos osztály hozzárendeli a mezőt, amikor először hívjuk meg getInstance.

3.3. Enum Singleton

Az utolsó megoldás a Hatékony Java könyvet (3. tétel) Joshua Block írta és egy enum a helyett osztály. Az írás idején ez tekinthető a legtömörebb és legbiztonságosabb módszernek a szingulett megírásához:

public enum EnumSingleton {INSTANCE; // egyéb módszerek ...}

4. Következtetés

Összefoglalva, ez a gyors cikk átnézte a kétszeresen ellenőrzött zárolási mintát, annak határait és néhány alternatívát.

A gyakorlatban a túlzott bőbeszédűség és a visszamenőleges kompatibilitás hiánya ezt a mintát hibára hajlamossá teszi, ezért kerülnünk kell. Ehelyett alternatívát kell használnunk, amely lehetővé teszi a JVM számára a szinkronizálást.

Mint mindig, az összes példa kódja elérhető a GitHubon.