Kétszeres ellenőrzés blokkolás | |
---|---|
Duplán ellenőrzött zár | |
Leírása: Tervezési minták | Nem |
A duplán ellenőrzött zár egy párhuzamos tervezési minta , amelyet a zár megszerzésével járó többletköltség csökkentésére terveztek . Először a blokkolási feltételt minden szinkronizálás nélkül ellenőrzik; a szál csak akkor próbálja megszerezni a zárat, ha az ellenőrzés eredménye azt jelzi, hogy meg kell szereznie a zárat.
Egyes nyelveken és/vagy egyes gépeken nem lehetséges ezt a mintát biztonságosan megvalósítani. Ezért néha anti-mintának is nevezik . Az ilyen tulajdonságok a Java memóriamodellben és a C++ memóriamodellben a szigorú " előtte történik " viszonyhoz vezettek .
Általában arra használják, hogy csökkentsék a többszálú programokban végzett lusta inicializálás költségeit , például a Singleton tervezési minta részeként . Egy változó lusta inicializálása esetén az inicializálás addig késik, amíg a változó értékére nincs szükség a számításban.
Tekintsük a következő Java -kódot , amelyet az [1] -ből vettünk :
// Egyszálas verzió osztály Foo { private Helper helper = null ; public Helper getHelper () { if ( helper == null ) helper = new Helper (); visszatérő segítő ; } // és az osztály többi tagja... }Ez a kód nem fog megfelelően működni egy többszálú programban. A metódusnak getHelper()zárolást kell szereznie abban az esetben, ha két szálból egyszerre hívják. Valójában, ha a mező helpermég nincs inicializálva, és két szál egyszerre hívja a metódust getHelper(), akkor mindkét szál megpróbál létrehozni egy objektumot, ami egy extra objektum létrehozásához vezet. Ezt a problémát szinkronizálással oldja meg, amint az a következő példában látható.
// Helyes, de "drága" többszálas verzió class Foo { private Helper helper = null ; public synchronized Helper getHelper () { if ( helper == null ) helper = new Helper (); visszatérő segítő ; } // és az osztály többi tagja... }Ez a kód működik, de további szinkronizálási költségeket vezet be. Az első hívás getHelper()létrehozza az objektumot, és csak az getHelper()objektum inicializálása során meghívott néhány szálat kell szinkronizálni. Az inicializálás után a hívási szinkronizálás getHelper()redundáns, mivel csak a változót olvassa be. Mivel a szinkronizálás akár 100-szorosára is csökkentheti a teljesítményt, szükségtelennek tűnik a zárolás többletköltsége minden alkalommal, amikor ezt a módszert hívják: az inicializálás befejezése után a zárra már nincs szükség. Sok programozó megpróbálta optimalizálni ezt a kódot a következőképpen:
Intuitív szinten ez a kód helyesnek tűnik. Van azonban néhány probléma (a Java 1.4-es és korábbi verzióiban, valamint a nem szabványos JRE-megvalósításokban), amelyeket el kell kerülni. Képzelje el, hogy egy többszálú program eseményei a következőképpen zajlanak:
A kétszeresen ellenőrzött zárolás egyik veszélye a J2SE 1.4 -ben (és korábbi verziókban) az, hogy a program gyakran megfelelően működik. Először is, a vizsgált helyzet nem fordul elő túl gyakran; másodszor, nehéz megkülönböztetni ennek a mintának a helyes megvalósítását a leírt problémával rendelkezőtől. A fordítótól , az ütemező által a szálakhoz rendelt processzoridő-kiosztástól és az egyidejűleg futó egyéb folyamatok természetétől függően a kétszeresen ellenőrzött zárolás helytelen megvalósítása által okozott hibák általában véletlenül fordulnak elő. Az ilyen hibák reprodukálása általában nehéz.
A problémát a J2SE 5.0 használatával oldhatja meg . Az új kulcsszószemantika volatileebben az esetben lehetővé teszi a változókba való írás helyes kezelését. Ezt az új mintát az [1] írja le :
// Új illékony szemantikával működik // Java 1.4-es és korábbi verzióiban nem működik a Foo volatile szemantikai osztály miatt { private volatile Helper helper = null ; public Helper getHelper () { if ( helper == null ) { synchronized ( this ) { if ( helper == null ) helper = new Helper (); } } return helper ; } // és az osztály többi tagja... }Számos kétszeresen ellenőrzött zárolási opciót javasoltak, amelyek nem jelzik kifejezetten (volatile vagy szinkronizálás révén), hogy egy objektum teljesen felépített, és mindegyik helytelen a Symantec JIT és a régi Oracle JRE-k számára [2] [3] .
A Microsoft megerősíti [4] , hogy a volatile kulcsszó használatakor biztonságos a Double Checked zárolási minta használata.
A következő Python -kód példát mutat a lusta inicializálásra a duplán ellenőrzött zárolási mintával kombinálva:
# Python2 vagy Python3 szükséges #-*- kódolás: UTF-8 *-* befűzés _ osztály SimpleLazyProxy : '''lusta objektum inicializálás cérnabiztos'''' def __init__ ( saját , gyári ): self . __lock = szálfűzés . RLock () self . __obj = Nincs saját . __gyár = gyár def __call__ ( self ): '''függvény a valós objektum eléréséhez ha az objektum nem jön létre, akkor létrejön''' # próbáljon meg "gyorsan" hozzáférni az objektumhoz: obj = self . __obj ha az obj nem Nincs : # sikerült! return obj else : # lehet, hogy az objektum még nem jött létre önmagával . _ __lock : # hozzáférést kap az objektumhoz exkluzív módban: obj = self . __obj ha az obj nem None : # kiderül, hogy az objektumot már létrehozták. # ne hozd újra return obj else : # az objektum még nem igazán jött létre. #alkossuk meg! obj = én . __gyár () önmaga . __obj = obj visszatérő obj __getattr__ = lambda self , név : \ getattr ( self (), név ) def lazy ( proxy_cls = SimpleLazyProxy ): '''dekorátor, amely egy osztályt lusta inicializálású osztállyá alakít a Proxy osztály segítségével''' class ClassDecorator : def __init__ ( self , cls ): # a dekorátor inicializálása, # de nem a díszített osztály és nem a proxy osztály önmaga . cls = cls def __call__ ( self , * args , ** kwargs ): # proxy osztály inicializálásának hívása # adja át a szükséges paramétereket a proxy osztálynak # a díszített osztály inicializálásához return proxy_cls ( lambda : self . cls ( * args , ** kwargs )) vissza ClassDecorator # egyszerű ellenőrzés: def teszt_0 (): print ( ' \t\t\t *** Teszt kezdete ***' ) behozatali idő @lazy () ennek az osztálynak # példányai lusta inicializált osztályok lesznek. TestType : def __init__ ( self , name ): print ( ' %s : Létrehozva...' % name ) # mesterségesen növelje az objektum létrehozási idejét # a szálverseny fokozása érdekében idő . aludni ( 3 ) önmaga . név = név print ( ' %s : Létrehozva !' % name ) def test ( self ): print ( ' %s : Tesztelés' % self . name ) # egy ilyen példány több szállal fog kölcsönhatásba lépni test_obj = TestType ( 'Inter-thread test object' ) target_event = szálfűzés . Event () def threads_target (): # függvény, amelyet a szálak végrehajtanak: # várjon egy különleges eseményre target_event . várj () # amint ez az esemény bekövetkezik - # mind a 10 szál egyszerre fog hozzáférni a # tesztobjektumhoz, és ebben a pillanatban az egyik test_obj szálban inicializálódik . teszt () # hozza létre ezt a 10 szálat a fenti algoritmussal threads_target() threads = [ ] szálhoz a ( 10 ) tartományban : thread = threading . Szál ( cél = thread_target ) szál . start () szálak . hozzáfűzés ( szál ) print ( 'Eddig nem volt hozzáférés az objektumhoz' ) # várj egy kicsit... idő . aludni ( 3 ) # ...és futtassa egyszerre a test_obj.test() parancsot az összes szálon print ( 'Tűzzel ki az eseményt a tesztobjektum használatához!' ) target_event . készlet () # szál vége a szálakban : szál . csatlakozz () print ( ' \t\t\t *** Teszt vége ***' )Tervezési minták | |
---|---|
Fő | |
Generatív | |
Szerkezeti | |
Viselkedési | |
Párhuzamos programozás |
|
építészeti |
|
Java EE sablonok | |
Egyéb sablonok | |
Könyvek | |
Személyiségek |