Tarkista esto | |
---|---|
Kaksoistarkistettu lukitus | |
Kuvattu suunnittelukuvioissa | Ei |
Kaksoistarkistettu lukitus on rinnakkainen suunnittelukuvio, joka on suunniteltu vähentämään lukon hankkimiseen liittyviä ylimääräisiä kustannuksia. Ensin estotila tarkistetaan ilman synkronointia; lanka yrittää hankkia lukon vain, jos tarkastuksen tulos osoittaa, että sen on hankittava lukko.
Joillakin kielillä ja/tai joillakin koneilla tätä mallia ei ole mahdollista toteuttaa turvallisesti. Siksi sitä kutsutaan joskus anti-kuvioksi . Tällaiset ominaisuudet ovat johtaneet tiukkaan järjestyksen " tapahtuu ennen " -suhteeseen Java-muistimallissa ja C++-muistimallissa.
Sitä käytetään yleisesti vähentämään laiskan alustuksen toteuttamista monisäikeisissä ohjelmissa, kuten osana Singleton-suunnittelumallia . Laisalla muuttujan alustuksella alustus viivästyy, kunnes muuttujan arvoa tarvitaan laskennassa.
Harkitse seuraavaa Java -koodia, joka on otettu kohteesta [1] :
// Yksisäikeinen versio luokka Foo { private Helper helper = null ; public Helper getHelper () { if ( helper == null ) helper = new Helper (); paluuauttaja ; _ } // ja muut luokan jäsenet... }Tämä koodi ei toimi oikein monisäikeisessä ohjelmassa. Menetelmän getHelper()on hankittava lukko, jos sitä kutsutaan samanaikaisesti kahdesta säikeestä. Itse asiassa, jos kenttää helperei ole vielä alustettu ja kaksi säiettä kutsuvat menetelmää samanaikaisesti getHelper(), molemmat säikeet yrittävät luoda objektin, mikä johtaa ylimääräisen objektin luomiseen. Tämä ongelma ratkaistaan käyttämällä synkronointia seuraavan esimerkin mukaisesti.
// Oikea, mutta "kallis" monisäikeinen versio class Foo { private Helper helper = null ; julkinen synkronoitu Apulainen getHelper ( ) { if ( helper == null ) helper = new Helper (); paluuauttaja ; _ } // ja muut luokan jäsenet... }Tämä koodi toimii, mutta se tuo ylimääräisiä synkronointikustannuksia. Ensimmäinen kutsu getHelper()luo objektin, ja vain muutamat säikeet, joita kutsutaan getHelper()objektin alustuksen aikana, on synkronoitava. Kun synkronointi on alustettu, puhelun synkronointi getHelper()on redundantti, koska se lukee vain muuttujan. Koska synkronointi voi vähentää suorituskykyä kertoimella 100 tai enemmän, lukitseminen joka kerta, kun tätä menetelmää kutsutaan, näyttää tarpeettomalta: kun alustus on valmis, lukkoa ei enää tarvita. Monet ohjelmoijat ovat yrittäneet optimoida tämän koodin seuraavasti:
Intuitiivisella tasolla tämä koodi näyttää oikealta. On kuitenkin joitain ongelmia (Java 1.4:ssä ja aiemmissa versioissa sekä epästandardeissa JRE-toteutuksissa), joita pitäisi ehkä välttää. Kuvittele, että tapahtumat monisäikeisessä ohjelmassa etenevät seuraavasti:
Yksi kaksoistarkistetun lukituksen käytön vaaroista J2SE 1.4 :ssä (ja aikaisemmissa) on, että ohjelma näyttää usein toimivan oikein. Ensinnäkin kyseessä oleva tilanne ei toistu kovin usein; toiseksi on vaikea erottaa tämän mallin oikea toteutus siitä, jolla on kuvattu ongelma. Riippuen kääntäjästä , ajastimen prosessoriajan kohdistamisesta säikeille ja muiden käynnissä olevien samanaikaisten prosessien luonteesta, kaksinkertaisesti tarkastetun lukituksen virheellisestä toteutuksesta aiheutuvia virheitä esiintyy yleensä sattumanvaraisesti. Tällaisten virheiden toistaminen on yleensä vaikeaa.
Voit ratkaista ongelman käyttämällä J2SE 5.0 :aa . Uusi avainsanasemantiikka volatilemahdollistaa muuttujaan kirjoittamisen oikein tässä tapauksessa. Tämä uusi malli on kuvattu kohdassa [1] :
// Toimii uuden haihtuvan semantiikan kanssa // Ei toimi Java 1.4:ssä ja aiemmissa versioissa haihtuvan semantiikan luokan Foo vuoksi { yksityinen haihtuva Helper helper = null ; public Helper getHelper () { if ( helper == null ) { synkronoitu ( this ) { if ( helper == null ) helper = new Helper (); } } paluuta avustaja ; } // ja muut luokan jäsenet... }On ehdotettu monia kaksinkertaisesti tarkistettuja lukitusvaihtoehtoja, jotka eivät nimenomaisesti (haihtuvien tai synkronoinnin kautta) osoita, että objekti on täysin rakennettu, ja ne kaikki ovat virheellisiä Symantec JIT:lle ja vanhoille Oracle JRE:ille [2] [3] .
Microsoft vahvistaa [4] , että haihtuvaa avainsanaa käytettäessä on turvallista käyttää Double Checked -lukituskuviota.
Seuraava Python - koodi näyttää esimerkkitoteutuksen laiskasta alustuksesta yhdessä kaksoistarkistetun lukitusmallin kanssa:
# vaativat Python2 tai Python3 #-*- koodauksen: UTF-8 *-* tuonti kierteitys luokka SimpleLazyProxy : '''laiska objektin alustus lankaturvallinen''' def __init__ ( itse , tehdas ): itse . __lock = ketjuttaminen . RLukitse () itse . __obj = Ei itse . __tehdas = tehdas def __call__ ( self ): ''''toiminto todellisen objektin käyttämiseksi jos objektia ei luoda, se luodaan''' # yritä saada "nopea" pääsy objektiin: obj = self . __obj jos obj ei ole Ei mitään : # onnistui! return obj else : # objektia ei ehkä ole vielä luotu itsensä kanssa . __lock : # päästä kohteeseen eksklusiivisessa tilassa: obj = self . __obj jos obj ei ole None : # osoittaa, että objekti on jo luotu. #älä luo sitä uudelleen return obj else : # objektia ei ole vielä oikein luotu. # luodaan se! obj = itse . __tehdas () itse . __obj = obj palauttaa obj __getattr__ = lambda itse , nimi : \ getattr ( itse (), nimi ) def lazy ( proxy_cls = SimpleLazyProxy ): '''sisustin, joka muuttaa luokan luokaksi laiskalla alustuksella välityspalvelinluokan avulla''' class ClassDecorator : def __init__ ( self , cls ): # sisustajan alustus, # mutta ei koristeltua luokkaa eikä välityspalvelinluokkaa itse . cls = cls def __call__ ( self , * args , ** kwargs ): # kutsu välityspalvelinluokan alustukseen # välitä tarvittavat parametrit välityspalvelinluokalle # alustaaksesi koristettavan luokan return proxy_cls ( lambda : itse . cls ( * args , ** kwargs )) palauta ClassDecorator # yksinkertainen tarkistus: def testi_0 (): tulosta ( ' \t\t\t *** Testin aloitus ***' ) tuontiaika _ @lazy () # tämän luokan esiintymiä ovat laiska-alustettu luokka TestType : def __init__ ( itse , nimi ): print ( ' %s : Luotu...' % name ) # lisää keinotekoisesti objektin luontiaikaa # lisätäksesi säikeen kilpailua aika . nukkua ( 3 ) itse . nimi = nimi print ( ' %s : Luotu ' % name ) def test ( self ): print ( ' %s : Testataan' % self . name ) # yksi tällainen ilmentymä on vuorovaikutuksessa useiden säikeiden kanssa test_obj = TestType ( 'Säikeiden välinen testiobjekti' ) kohdetapahtuma = ketjuttaminen . Tapahtuma () def threads_target (): # toiminto, jonka säikeet suorittavat: # odota erityistä tapahtumaa target_event . odota () # heti tämän tapahtuman tapahtuessa - # kaikki 10 säiettä pääsevät samanaikaisesti testiobjektiin # ja tällä hetkellä se alustetaan johonkin säikeistä test_obj . testi () # luo nämä 10 säiettä yllä olevalla algoritmilla threads_target() threads = [ ] säikeelle alueella ( 10 ): thread = threading . Säie ( kohde = thread_target ) lanka . aloita () säikeet . liittää ( lanka ) print ( 'Objektiin ei ole tähän mennessä ollut pääsyä' ) # odota vähän... aika . nukkua ( 3 ) # ...ja suorita test_obj.test() samanaikaisesti kaikissa säikeissä print ( 'Sammuta tapahtuma käyttää testiobjektia!' ) target_event . asettaa () # langan loppu säikeissä : lanka . _ liity () tulosta ( ' \t\t\t *** Testin loppu ***' )Suunnittelumalleja | |
---|---|
Main | |
Generatiivinen | |
Rakenteellinen | |
Käyttäytyminen | |
Rinnakkaisohjelmointi _ |
|
arkkitehtoninen |
|
Java EE -mallit | |
Muut mallit | |
Kirjat | |
Persoonallisuudet |