AtomicInteger og flygtig
Jeg tror, at Atomic*
giver faktisk begge dele atomicitet og flygtighed. Så når du ringer (siger) AtomicInteger.get()
, er du garanteret at få det nyeste værdi. Dette er dokumenteret i java.util.concurrent.atomic
pakkedokumentation:
Hukommelseseffekterne for adgang og opdateringer af atomics følger generelt reglerne for flygtige stoffer, som angivet i afsnit 17.4 i Java™ Language Specification.
- get har hukommelseseffekterne ved at læse en flygtig variabel.
- sæt har hukommelseseffekterne ved at skrive (tildele) en flygtig variabel.
- lazySet har hukommelseseffekterne ved at skrive (tildele) en flygtig variabel, bortset fra at den tillader genbestillinger med efterfølgende (men ikke tidligere) hukommelseshandlinger, der ikke i sig selv pålægger genbestillingsbegrænsninger med almindelige ikke-flygtige skrivninger. Blandt andre brugskontekster kan> - lazySet gælde, når en reference, der aldrig tilgås igen, nulstilles af hensyn til affaldsindsamling.
- weakCompareAndSet læser atomisk og betinget skriver en variabel, men opretter ingen sker-før-ordrer, så det giver ingen garantier med hensyn til tidligere eller efterfølgende læsninger og skrivninger af andre variabler end målet for weakCompareAndSet.
- compareAndSet og alle andre læse-og-opdater-operationer, såsom getAndIncrement, har hukommelseseffekterne ved både at læse og skrive flygtige variable.
Hvis du nu har
volatile AtomicInteger count;
volatile
del betyder, at hver tråd vil bruge den seneste AtomicInteger
reference, og det faktum, at det er en AtomicInteger
betyder, at du også se den seneste værdi for det pågældende objekt.
Det er ikke almindeligt (IME) at have brug for dette - for normalt ville du ikke gentildele count
at henvise til et andet objekt. I stedet ville du have:
private final AtomicInteger count = new AtomicInteger();
På det tidspunkt, det faktum, at det er en final
variabel betyder, at alle tråde vil beskæftige sig med det samme objekt - og det faktum, at det er en Atomic*
objekt betyder, at de vil se den seneste værdi i det pågældende objekt.
Jeg vil sige nej, det er ikke trådsikkert, hvis du definerer trådsikkert som at have det samme resultat under enkelttrådstilstand og multitrådstilstand. I enkelttrådstilstand vil antallet aldrig blive større end 10, men i multitrådstilstand kan det.
Problemet er, at get
og incrementAndGet
er atomart, men en if
er ikke. Husk, at en ikke-atomar operation kan standses til enhver tid. For eksempel:
count = 9
i øjeblikket.- Tråd A kører
if(count.get() <10)
og fårtrue
og stoppede der. - Tråd B kører
if(count.get() <10)
og fårtrue
også, så den kørercount.incrementAndGet()
og afslutter. Nucount = 10
. - Tråd A genoptages og kører
count.incrementAndGet()
, nucount = 11
hvilket aldrig vil ske i enkelttrådstilstand.
Hvis du vil gøre det trådsikkert uden at bruge synchronized
som er langsommere, prøv denne implementering i stedet:
class A{
final AtomicInteger count;
void someMethod(){
// do something
if(count.getAndIncrement() <10){
// safe now
} else count.getAndDecrement(); // rollback so this thread did nothing to count
}
For at bevare den originale semantik og understøtte flere tråde kan du gøre noget som:
public class A {
private AtomicInteger count = new AtomicInteger(0);
public void someMethod() {
int i = count.get();
while (i < 10 && !count.compareAndSet(i, i + 1)) {
i = count.get();
}
}
}
Dette undgår, at enhver tråd nogensinde ser antallet nå 10.