Dobbelttjek blokering | |
---|---|
Dobbelttjekket låsning | |
Beskrevet i Design Patterns | Ikke |
Dobbelt tjekket låsning er et parallelt designmønster designet til at reducere det overhead, der er forbundet med at få en lås. Først kontrolleres blokeringstilstanden uden nogen synkronisering; tråden forsøger kun at erhverve låsen, hvis resultatet af kontrollen indikerer, at den skal erhverve låsen.
På nogle sprog og/eller på nogle maskiner er det ikke muligt at implementere dette mønster sikkert. Derfor kaldes det nogle gange et anti-mønster . Sådanne funktioner har ført til den strenge rækkefølge " sker før "-forholdet i Java Memory Model og C++ Memory Model.
Det bruges almindeligvis til at reducere omkostningerne ved at implementere doven initialisering i flertrådede programmer, såsom som en del af Singleton-designmønsteret . Med doven initialisering af en variabel forsinkes initialiseringen, indtil værdien af variablen er nødvendig i beregningen.
Overvej følgende Java -kode hentet fra [1] :
// Single threaded version class Foo { private Helper helper = null ; public Helper getHelper () { if ( helper == null ) helper = new Helper (); returnere hjælper ; } // og andre medlemmer af klassen... }Denne kode vil ikke fungere korrekt i et program med flere tråde. Metoden getHelper()skal anskaffe en lås, hvis den kaldes samtidigt fra to tråde. Faktisk, hvis feltet helperendnu ikke er blevet initialiseret, og to tråde kalder metoden på samme tid getHelper(), vil begge tråde forsøge at skabe et objekt, hvilket vil føre til oprettelsen af et ekstra objekt. Dette problem løses ved at bruge synkronisering, som vist i følgende eksempel.
// Korrekt, men "dyr" multi-threaded version klasse Foo { private Helper helper = null ; public synchronized Helper getHelper () { if ( helper == null ) helper = new Helper (); returnere hjælper ; } // og andre medlemmer af klassen... }Denne kode virker, men den introducerer yderligere synkroniseringsomkostninger. Det første kald getHelper()vil oprette objektet, og kun de få tråde, der vil blive kaldt getHelper()under objektinitialisering, skal synkroniseres. Når først den er initialiseret, er synkroniseringen ved opkald getHelper()redundant, da den kun vil læse variablen. Da synkronisering kan reducere ydeevnen med en faktor på 100 eller mere, virker overheaden ved låsning, hver gang denne metode kaldes, unødvendig: når først initialiseringen er fuldført, er låsen ikke længere nødvendig. Mange programmører har forsøgt at optimere denne kode på denne måde:
På et intuitivt niveau virker denne kode korrekt. Der er dog nogle problemer (i Java 1.4 og tidligere og ikke-standard JRE-implementeringer), som måske bør undgås. Forestil dig, at begivenheder i et flertrådet program forløber således:
En af farerne ved at bruge dobbelttjekket låsning i J2SE 1.4 (og tidligere) er, at programmet ofte ser ud til at fungere korrekt. For det første vil den betragtede situation ikke forekomme særlig ofte; for det andet er det svært at skelne den korrekte implementering af dette mønster fra den, der har det beskrevne problem. Afhængigt af compileren , planlæggerens tildeling af processortid til tråde og arten af andre kørende samtidige processer, opstår fejl forårsaget af forkert implementering af dobbelttjekket låsning normalt tilfældigt. Det er normalt vanskeligt at gengive sådanne fejl.
Du kan løse problemet ved at bruge J2SE 5.0 . Det nye søgeord semantik volatilegør det muligt at håndtere skrivning til en variabel korrekt i dette tilfælde. Dette nye mønster er beskrevet i [1] :
// Fungerer med ny flygtig semantik // Virker ikke i Java 1.4 og tidligere på grund af flygtig semantikklasse Foo { private volatile Helper helper = null ; public Helper getHelper () { if ( helper == null ) { synchronized ( this ) { if ( helper == null ) helper = new Helper (); } } returner hjælper ; } // og andre medlemmer af klassen... }Mange dobbelttjekkede låsemuligheder er blevet foreslået, som ikke eksplicit (via flygtig eller synkronisering) indikerer, at et objekt er fuldt konstrueret, og alle af dem er forkerte for Symantec JIT og ældre Oracle JRE'er [2] [3] .
Microsoft bekræfter [4] , at når du bruger det flygtige nøgleord, er det sikkert at bruge det dobbelttjekkede låsemønster.
Den følgende Python -kode viser et eksempel på implementering af doven initialisering i kombination med det dobbelttjekkede låsemønster:
# kræver Python2 eller Python3 #-*- kodning: UTF-8 *-* importere gevind klasse SimpleLazyProxy : '''initialisering af dovent objekt trådsikker''' def __init__ ( selv , fabrik ): selv . __lås = trådning . Lås () selv . __obj = Ingen selv . __fabrik = fabrik def __call__ ( self ): '''funktion for at få adgang til det rigtige objekt hvis objektet ikke er oprettet, bliver det oprettet''' # prøv at få "hurtig" adgang til objektet: obj = self . __obj hvis obj ikke er Ingen : # lykkedes! return obj else : # objektet er muligvis ikke oprettet endnu med sig selv . __lock : # få adgang til objektet i eksklusiv tilstand: obj = self . __obj hvis obj ikke er Ingen : # viser sig, at objektet allerede er oprettet. # genskab det ikke return obj else : # objektet er ikke rigtig blevet oprettet endnu. # lad os skabe det! obj = selv . __fabrik () selv . __obj = obj returnere obj __getattr__ = lambda selv , navn : \ getattr ( selv (), navn ) def lazy ( proxy_cls = SimpleLazyProxy ): '''dekorator, der gør en klasse til en klasse med doven initialisering ved hjælp af Proxy-klassen''' klasse ClassDecorator : def __init__ ( self , cls ): # initialisering af dekoratøren, # men ikke klassen, der dekoreres og ikke proxy-klassen selv . cls = cls def __call__ ( selv , * args , ** kwargs ): # opkald til proxy klasse initialisering # videregive de nødvendige parametre til Proxy-klassen # for at initialisere klassen, der dekoreres returner proxy_cls ( lambda : self . cls ( * args , ** kwargs )) returner ClassDecorator # simpel kontrol: def test_0 (): print ( ' \t\t\t *** Teststart ***' ) import tid @lazy () # forekomster af denne klasse vil være doven-initialiseret klasse TestType : def __init__ ( selv , navn ): print ( ' %s : Oprettet...' % navn ) # kunstigt øge objektoprettelsestiden # for at øge trådkonkurrencen tid . søvn ( 3 ) selv . navn = navn print ( ' %s : Oprettet!' % navn ) def test ( self ): print ( ' %s : Testing ' % self . name ) # en sådan instans vil interagere med flere tråde test_obj = TestType ( 'Inter-thread testobjekt' ) target_event = trådning . Hændelse () def threads_target (): # funktion, som tråde vil udføre: # vent på en speciel begivenhed target_event . vent () # så snart denne hændelse opstår - # vil alle 10 tråde samtidigt få adgang til testobjektet # og i dette øjeblik initialiseres det i en af trådene test_obj . test () # opret disse 10 tråde med ovenstående algoritme threads_target() tråde = [] for tråd i området ( 10 ): tråd = trådning . Tråd ( target = thread_target ) tråd . start () tråde . tilføje ( tråd ) print ( 'Der har ikke været nogen adgang til objektet indtil nu' ) #vent lidt... tid . søvn ( 3 ) # ...og kør test_obj.test() samtidigt på alle tråde print ( 'Brand hændelse for at bruge testobjekt!' ) target_event . sæt () # ende for tråd i tråde : tråd . deltage () print ( ' \t\t\t *** Slut på test ***' )Design mønstre | |
---|---|
Hoved | |
Generativ | |
Strukturel | |
Adfærdsmæssigt | |
Parallel programmering |
|
arkitektonisk |
|
Java EE skabeloner | |
Andre skabeloner | |
Bøger | |
Personligheder |