Java >> Java tutorial >  >> Tag >> synchronized

Hvad er forskellen mellem atomisk / flygtig / synkroniseret?

Du spørger specifikt om, hvordan de internt fungerer , så her er du:

Ingen synkronisering

private int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

Det læser dybest set værdi fra hukommelsen, øger den og sætter tilbage til hukommelsen. Dette fungerer i en enkelt tråd, men i dag, i en æra med multi-core, multi-CPU, multi-level caches vil det ikke fungere korrekt. Først og fremmest introducerer det race tilstand (flere tråde kan læse værdien på samme tid), men også problemer med synlighed. Værdien gemmes muligvis kun i "lokalt " CPU-hukommelse (nogle cache) og ikke være synlig for andre CPU'er/kerner (og dermed - tråde). Det er derfor, mange henviser til lokal kopi af en variabel i en tråd. Det er meget usikkert. Overvej denne populære, men ødelagte trådstoppende kode:

private boolean stopped;

public void run() {
    while(!stopped) {
        //do some work
    }
}

public void pleaseStop() {
    stopped = true;
}

Tilføj volatile til stopped variabel, og den fungerer fint - hvis en anden tråd ændrer stopped variabel via pleaseStop() metode, er du garanteret at se den ændring med det samme i arbejdstrådens while(!stopped) sløjfe. BTW dette er heller ikke en god måde at afbryde en tråd på, se:Sådan stopper du en tråd der kører for evigt uden brug og Stop en specifik java-tråd.

AtomicInteger

private AtomicInteger counter = new AtomicInteger();

public int getNextUniqueIndex() {
  return counter.getAndIncrement();
}

AtomicInteger klasse bruger CAS (sammenlign-og-byt) CPU-operationer på lavt niveau (ingen synkronisering nødvendig!) De tillader dig kun at ændre en bestemt variabel, hvis den nuværende værdi er lig med noget andet (og returneres med succes). Så når du udfører getAndIncrement() det kører faktisk i en løkke (forenklet reel implementering):

int current;
do {
  current = get();
} while(!compareAndSet(current, current + 1));

Så grundlæggende:læs; prøv at gemme øget værdi; hvis det ikke lykkedes (værdien er ikke længere lig med current). ), læs og prøv igen. compareAndSet() er implementeret i native kode (assembly).

volatile uden synkronisering

private volatile int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

Denne kode er ikke korrekt. Det løser synlighedsproblemet (volatile sørger for, at andre tråde kan se ændringer foretaget til counter ), men har stadig en racetilstand. Dette er blevet forklaret flere gange:før/efter-inkrementering er ikke atomart.

Den eneste bivirkning af volatile er "skyller " cacher, så alle andre parter ser den nyeste version af dataene. Dette er for strengt i de fleste situationer; det er derfor volatile er ikke standard.

volatile uden synkronisering (2)

volatile int i = 0;
void incIBy5() {
  i += 5;
}

Det samme problem som ovenfor, men endnu værre fordi i er ikke private . Løbets tilstand er stadig til stede. Hvorfor er det et problem? Hvis f.eks. to tråde kører denne kode samtidigt, kan outputtet være + 5 eller + 10 . Du vil dog med garanti se ændringen.

Flere uafhængige synchronized

void incIBy5() {
  int temp;
  synchronized(i) { temp = i }
  synchronized(i) { i = temp + 5 }
}

Overraskelse, denne kode er også forkert. Faktisk er det helt forkert. Først og fremmest synkroniserer du på i , som er ved at blive ændret (desuden i er en primitiv, så jeg gætter på, at du synkroniserer på en midlertidig Integer oprettet via autoboxing...) Fuldstændig mangelfuld. Du kan også skrive:

synchronized(new Object()) {
  //thread-safe, SRSLy?
}

Ikke to tråde kan indtaste den samme synchronized blok med samme lås . I dette tilfælde (og på samme måde i din kode) ændres låseobjektet ved hver udførelse, så synchronized har faktisk ingen effekt.

Også selvom du har brugt en endelig variabel (eller this ) til synkronisering er koden stadig forkert. To tråde kan først læse i til temp synkront (har samme værdi lokalt i temp ), så tildeler den første en ny værdi til i (f.eks. fra 1 til 6), og den anden gør det samme (fra 1 til 6).

Synkroniseringen skal strække sig fra læsning til tildeling af en værdi. Din første synkronisering har ingen effekt (læser en int). er atomær) og den anden også. Efter min mening er disse de korrekte former:

void synchronized incIBy5() {
  i += 5 
}

void incIBy5() {
  synchronized(this) {
    i += 5 
  }
}

void incIBy5() {
  synchronized(this) {
    int temp = i;
    i = temp + 5;
  }
}

Erklære en variabel som flygtig betyder, at ændring af dens værdi øjeblikkeligt påvirker den faktiske hukommelseslagring for variablen. Compileren kan ikke optimere eventuelle referencer til variablen. Dette garanterer, at når en tråd ændrer variablen, ser alle andre tråde den nye værdi med det samme. (Dette er ikke garanteret for ikke-flygtige variabler.)

Erklærer et atomart variabel garanterer, at operationer udført på variablen sker på en atomisk måde, dvs. at alle undertrinene af operationen er fuldført inden for den tråd, de udføres og ikke afbrydes af andre tråde. For eksempel kræver en inkrement-og-test-operation, at variablen inkrementeres og derefter sammenlignes med en anden værdi; en atomoperation garanterer, at begge disse trin vil blive gennemført, som om de var en enkelt udelelig/uafbrydelig operation.

Synkroniserer al adgang til en variabel tillader kun en enkelt tråd ad gangen at få adgang til variablen og tvinger alle andre tråde til at vente på, at den adgangstråd frigiver sin adgang til variablen.

Synkroniseret adgang ligner atomadgang, men atomoperationerne implementeres generelt på et lavere niveau af programmering. Det er også fuldt ud muligt kun at synkronisere nogle adgange til en variabel og tillade andre adgange at blive usynkroniserede (f.eks. synkronisere alle skrivninger til en variabel, men ingen af ​​læsningerne fra den).

Atomicitet, synkronisering og volatilitet er uafhængige attributter, men bruges typisk i kombination for at gennemtvinge korrekt trådsamarbejde for at få adgang til variabler.

Tillæg (april 2016)

Synkroniseret adgang til en variabel implementeres normalt ved hjælp af en monitor eller semafor . Disse er mutex på lavt niveau (gensidig udelukkelse) mekanismer, der tillader en tråd udelukkende at erhverve kontrol over en variabel eller kodeblok, hvilket tvinger alle andre tråde til at vente, hvis de også forsøger at erhverve den samme mutex. Når den ejer tråd frigiver mutex, kan en anden tråd erhverve mutex på skift.

Tillæg (juli 2016)

Synkronisering sker på et objekt . Dette betyder, at kald af en synkroniseret metode for en klasse vil låse this genstand for opkaldet. Statiske synkroniserede metoder låser Class objektet selv.

Ligeledes kræver indtastning af en synkroniseret blok låsning af this genstand for metoden.

Dette betyder, at en synkroniseret metode (eller blok) kan udføres i flere tråde på samme tid, hvis de låser på forskellige objekter, men kun én tråd kan udføre en synkroniseret metode (eller blok) ad gangen for en given enkelt objekt.


flygtig:

volatile er et nøgleord. volatile tvinger alle tråde til at hente den seneste værdi af variablen fra hovedhukommelsen i stedet for cachen. Der kræves ingen låsning for at få adgang til flygtige variabler. Alle tråde kan få adgang til flygtige variabelværdier på samme tid.

Bruger volatile variabler reducerer risikoen for hukommelseskonsistensfejl, fordi enhver skrivning til en flygtig variabel etablerer et sker-før-forhold med efterfølgende læsninger af den samme variabel.

Det betyder, at ændringer til en volatile variable er altid synlige for andre tråde . Hvad mere er, betyder det også, at når en tråd læser en volatile variabel, den ser ikke kun den seneste ændring af det flygtige, men også bivirkningerne af koden, der førte til ændringen .

Hvornår skal den bruges:Én tråd ændrer dataene, og andre tråde skal læse den seneste værdi af data. Andre tråde vil tage nogle handlinger, men de opdaterer ikke data .

AtomicXXX:

AtomicXXX klasser understøtter låsefri trådsikker programmering på enkelte variable. Disse AtomicXXX klasser (som AtomicInteger ) løser hukommelsesinkonsistensfejl/bivirkninger af modifikation af flygtige variabler, som er blevet tilgået i flere tråde.

Hvornår skal det bruges:Flere tråde kan læse og ændre data.

synkroniseret:

synchronized er nøgleord, der bruges til at beskytte en metode eller kodeblok. Ved at gøre metoden som synkroniseret har to effekter:

  1. For det første er det ikke muligt for to påkaldelser af synchronized metoder på det samme objekt til at interleave. Når en tråd udfører en synchronized metode for et objekt, alle andre tråde, der kalder synchronized metoder for den samme objektblok (suspend udførelse), indtil den første tråd er færdig med objektet.

  2. For det andet, når en synchronized metode afsluttes, etablerer den automatisk et sker-før-forhold med enhver efterfølgende påkaldelse af en synchronized metode til det samme objekt. Dette garanterer, at ændringer i objektets tilstand er synlige for alle tråde.

Hvornår skal du bruge:Flere tråde kan læse og ændre data. Din forretningslogik opdaterer ikke kun dataene, men udfører også atomoperationer

AtomicXXX svarer til volatile + synchronized selvom implementeringen er anderledes. AmtomicXXX udvider volatile variabler + compareAndSet metoder, men bruger ikke synkronisering.

Relaterede SE-spørgsmål:

Forskellen mellem flygtig og synkroniseret i Java

Flygtig boolesk vs AtomicBoolean

Gode ​​artikler at læse:(Ovenstående indhold er taget fra disse dokumentationssider)

https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html


Java tag