Java >> Java tutorial >  >> Tag >> volatile

Flygtig vs Atomic

Effekten af ​​volatile nøgleordet er cirka, at hver enkelt læse- eller skriveoperation på den variabel er atomisk.

Især dog en handling, der kræver mere end én læsning/skrive--såsom i++ , hvilket svarer til i = i + 1 , som man læser og én skriver -- er ikke atomic, da en anden tråd kan skrive til i mellem læse og skrive.

Atomic klasser, såsom AtomicInteger og AtomicReference , giver et bredere udvalg af operationer atomært, specifikt inklusive stigning for AtomicInteger .


Volatile og Atomic er to forskellige begreber. Volatile sikrer, at en bestemt, forventet (hukommelses)tilstand er sand på tværs af forskellige tråde, mens Atomics sikrer, at operation på variable udføres atomisk.

Tag følgende eksempel på to tråde i Java:

Tråd A:

value = 1;
done = true;

Tråd B:

if (done)
  System.out.println(value);

Starter med value = 0 og done = false reglen om trådning fortæller os, at det er udefineret, om tråd B vil udskrive værdi. Yderligere værdi er også udefineret på det tidspunkt! For at forklare dette skal du vide lidt om Java-hukommelseshåndtering (som kan være kompleks), kort sagt:Tråde kan skabe lokale kopier af variabler, og JVM'en kan omarrangere kode for at optimere den, derfor er der ingen garanti for, at ovenstående kode køres i præcis den rækkefølge. Indstilling udført til sand og derefter indstilling af værdien til 1 kunne være et muligt resultat af JIT-optimeringerne.

volatile sikrer kun, at i det øjeblik, hvor en sådan variabel får adgang, vil den nye værdi være umiddelbart synlig for alle andre tråde og udførelsesrækkefølgen sikrer, at koden er i den tilstand, du ville forvente, at den var. Så i tilfælde af koden ovenfor, definere done som flygtig vil sikre, at når Thread B kontrollerer variablen, er den enten falsk eller sand, og hvis den er sand, så value er også indstillet til 1.

Som en bivirkning af flygtig , er værdien af ​​en sådan variabel sat atomært i tråden (til en meget lille omkostning for udførelseshastighed). Dette er dog kun vigtigt på 32-bit systemer, dvs. brug lange (64-bit) variabler (eller lignende), i de fleste andre tilfælde er indstilling/læsning af en variabel alligevel atomisk. Men der er en vigtig forskel mellem en atom-adgang og en atom-operation. Volatile sikrer kun, at adgangen er atomisk, mens Atomics sikrer, at driften er atomært.

Tag følgende eksempel:

i = i + 1;

Uanset hvordan du definerer i, kan en anden tråd, der læser værdien, lige når ovenstående linje udføres, få i eller i + 1, fordi operationen er ikke atommæssigt. Hvis den anden tråd sætter i til en anden værdi, kunne jeg i værste fald sættes tilbage til hvad den var før af tråd A, fordi den var lige midt i at beregne i + 1 ud fra den gamle værdi, og derefter sætte i igen til den gamle værdi + 1. Forklaring:

Assume i = 0
Thread A reads i, calculates i+1, which is 1
Thread B sets i to 1000 and returns
Thread A now sets i to the result of the operation, which is i = 1

Atomics som AtomicInteger sikrer, at sådanne operationer sker atomært. Så ovenstående problem kan ikke ske, jeg ville enten være 1000 eller 1001, når begge tråde er afsluttet.


Der er to vigtige begreber i multithreading-miljø:

  1. atomicitet
  2. synlighed

volatile søgeord udrydder synlighedsproblemer, men det beskæftiger sig ikke med atomicitet. volatile vil forhindre compileren i at omarrangere instruktioner, der involverer en skrivning og en efterfølgende læsning af en flygtig variabel; for eksempel. k++ .Her, k++ er ikke en enkelt maskininstruktion, men tre:

  1. kopier værdien til et register;
  2. øge værdien;
  3. placer den tilbage.

Så selvom du erklærer en variabel som volatile , dette vil ikke gøre denne operation atomær; dette betyder, at en anden tråd kan se et mellemresultat, som er en forældet eller uønsket værdi for den anden tråd.

På den anden side AtomicInteger , AtomicReference er baseret på Sammenlign og swap-instruktionen. CAS har tre operander:en hukommelsesplacering V den forventede gamle værdi A , og den nye værdi B . CAS atomisk opdaterer V til den nye værdi B , men kun hvis værdien i V matcher den forventede gamle værdi A; ellers gør det ingenting. I begge tilfælde returnerer den værdien i øjeblikket i V . compareAndSet() metoder til AtomicInteger og AtomicReference drage fordel af denne funktionalitet, hvis den understøttes af den underliggende processor; hvis det ikke er det, implementerer JVM det via spin lock.


Java tag