Java Language
Atoomtypen
Zoeken…
Invoering
Java Atomic Types zijn eenvoudige muteerbare typen die basisbewerkingen bieden die draadveilig en atomair zijn zonder toevlucht te nemen tot vergrendeling. Ze zijn bedoeld voor gebruik in gevallen waarin vergrendeling een bottleneck van de gelijktijdigheid zou zijn, of waar er een risico op deadlock of livelock bestaat.
parameters
Parameter | Beschrijving |
---|---|
reeks | Vluchtige set van het veld |
krijgen | Vluchtig lezen van het veld |
lazySet | Dit is een in de winkel geordende bewerking van het veld |
compareAndSet | Als de waarde de uitgaande waarde is, stuur deze dan naar de nieuwe waarde |
getAndSet | haal de huidige waarde op en update |
Opmerkingen
Veel op in wezen combinaties van vluchtig lezen of schrijven en CAS- bewerkingen. De beste manier om dit te begrijpen, is door direct naar de broncode te kijken. Bijvoorbeeld AtomicInteger , Unsafe.getAndSet
Atoomtypen maken
Voor eenvoudige code met meerdere threads is synchronisatie acceptabel. Het gebruik van synchronisatie heeft echter een impact op de levendigheid en naarmate een codebase complexer wordt, neemt de kans toe dat je met Deadlock , Starvation of Livelock eindigt .
In geval van meer complexe gelijktijdigheid, is het gebruik van Atomic Variables vaak een beter alternatief, omdat hierdoor een individuele variabele op een thread-veilige manier kan worden benaderd zonder de overhead van het gebruik van gesynchroniseerde methoden of codeblokken.
Een AtomicInteger
type maken:
AtomicInteger aInt = new AtomicInteger() // Create with default value 0
AtomicInteger aInt = new AtomicInteger(1) // Create with initial value 1
Evenzo voor andere instantietypen.
AtomicIntegerArray aIntArray = new AtomicIntegerArray(10) // Create array of specific length
AtomicIntegerArray aIntArray = new AtomicIntegerArray(new int[] {1, 2, 3}) // Initialize array with another array
Evenzo voor andere atoomtypen.
Er is een opmerkelijke uitzondering dat er geen float
en double
typen zijn. Deze kunnen worden gesimuleerd door het gebruik van Float.floatToIntBits(float)
en Float.intBitsToFloat(int)
voor float
en Double.doubleToLongBits(double)
en Double.longBitsToDouble(long)
voor dubbels.
Als u bereid bent sun.misc.Unsafe
te gebruiken, kunt u elke primitieve variabele als atomair gebruiken door de atomaire bewerking in sun.misc.Unsafe
. Alle primitieve typen moeten worden omgezet of gecodeerd in int of longs om het op deze manier te gebruiken. Zie voor meer informatie: sun.misc.Unsafe .
Motivatie voor atoomtypen
De eenvoudige manier om toepassingen met meerdere threads te implementeren, is door de ingebouwde synchronisatie- en vergrendelingsprimitieven van Java te gebruiken; bijv. het synchronized
trefwoord. Het volgende voorbeeld laat zien hoe we synchronized
kunnen gebruiken om tellingen te verzamelen.
public class Counters {
private final int[] counters;
public Counters(int nosCounters) {
counters = new int[nosCounters];
}
/**
* Increments the integer at the given index
*/
public synchronized void count(int number) {
if (number >= 0 && number < counters.length) {
counters[number]++;
}
}
/**
* Obtains the current count of the number at the given index,
* or if there is no number at that index, returns 0.
*/
public synchronized int getCount(int number) {
return (number >= 0 && number < counters.length) ? counters[number] : 0;
}
}
Deze implementatie zal correct werken. Als u echter een groot aantal threads meerdere gelijktijdige oproepen op hetzelfde object Counters
uitvoert, kan de synchronisatie een bottleneck zijn. Concreet:
- Elke
synchronized
methode-aanroep begint met de huidige thread die het slot voor de instantieCounters
. - De draad zal het slot vast te houden terwijl het controleert
number
waarde en werkt de teller. - Ten slotte zal het het slot ontgrendelen, waardoor andere threads toegang krijgen.
Als een thread probeert het slot te verkrijgen terwijl een andere het vasthoudt, wordt de poging tot thread geblokkeerd (gestopt) in stap 1 totdat het slot wordt vrijgegeven. Als er meerdere threads wachten, krijgt een van hen deze en blijven de andere geblokkeerd.
Dit kan tot een aantal problemen leiden:
Als er veel twist is over het slot (dat wil zeggen dat veel threads proberen het te verkrijgen), kunnen sommige threads lange tijd worden geblokkeerd.
Wanneer een thread wordt geblokkeerd in afwachting van het slot, zal het besturingssysteem meestal proberen de uitvoering naar een andere thread te schakelen. Deze context switching loopt een relatief grote invloed op de prestaties van de processor.
Wanneer er meerdere threads op hetzelfde slot zijn geblokkeerd, zijn er geen garanties dat een van hen "redelijk" zal worden behandeld (dat wil zeggen dat elke thread is gepland om te worden uitgevoerd). Dit kan leiden tot uithongering van de draad .
Hoe implementeer je atoomtypen?
Laten we beginnen met het herschrijven van het bovenstaande voorbeeld met behulp van AtomicInteger
tellers:
public class Counters {
private final AtomicInteger[] counters;
public Counters(int nosCounters) {
counters = new AtomicInteger[nosCounters];
for (int i = 0; i < nosCounters; i++) {
counters[i] = new AtomicInteger();
}
}
/**
* Increments the integer at the given index
*/
public void count(int number) {
if (number >= 0 && number < counters.length) {
counters[number].incrementAndGet();
}
}
/**
* Obtains the current count of the object at the given index,
* or if there is no number at that index, returns 0.
*/
public int getCount(int number) {
return (number >= 0 && number < counters.length) ?
counters[number].get() : 0;
}
}
We hebben de int[]
door een AtomicInteger[]
en geïnitialiseerd met een instantie in elk element. We hebben ook aanroepen toegevoegd aan incrementAndGet()
en get()
in plaats van bewerkingen op int
waarden.
Maar het belangrijkste is dat we het synchronized
trefwoord kunnen verwijderen omdat vergrendeling niet langer nodig is. Dit werkt omdat de bewerkingen incrementAndGet()
en get()
atomair en thread-safe zijn . In deze context betekent dit dat:
Elke teller in de array kan alleen worden waargenomen in de "voor" -status voor een bewerking (zoals een "increment") of in de "na" -status.
Ervan uitgaande dat de bewerking plaatsvindt op tijdstip
T
, kan geen enkele thread de "vóór" -status na tijdstipT
.
Bovendien, terwijl twee threads mogelijk tegelijkertijd proberen dezelfde AtomicInteger
instantie tegelijkertijd bij te werken, zorgen de implementaties van de bewerkingen ervoor dat er slechts één stap per keer gebeurt op de gegeven instantie. Dit gebeurt zonder vergrendeling, wat vaak resulteert in betere prestaties.
Hoe werken atoomtypen?
Atoomtypen vertrouwen meestal op gespecialiseerde hardware-instructies in de instructieset van de doelmachine. Op Intel gebaseerde instructiesets bieden bijvoorbeeld een CAS
-instructie ( Compare and Swap ) die een specifieke reeks geheugenbewerkingen atomisch uitvoert.
Deze instructies op laag niveau worden gebruikt om bewerkingen op hoger niveau in de API's van de respectieve AtomicXxx
klassen te AtomicXxx
. Bijvoorbeeld (nogmaals, in C-achtige pseudocode):
private volatile num;
int increment() {
while (TRUE) {
int old = num;
int new = old + 1;
if (old == compare_and_swap(&num, old, new)) {
return new;
}
}
}
Als er geen discussie is over de AtomicXxxx
, zal de if
test slagen en wordt de lus onmiddellijk beëindigd. Als er twist is, dan zal de if
falen voor alle behalve één van de threads, en ze zullen "draaien" in de lus gedurende een klein aantal cycli van de lus. In de praktijk is het spinnen sneller orde van grootte (behalve bij onrealistisch hoge conflictniveaus, waar gesynchroniseerd beter presteert dan atoomklassen omdat wanneer de CAS-bewerking mislukt, de nieuwe poging alleen meer contentie toevoegt) dan de thread opschorten en naar een andere overschakelen een.
Overigens worden CAS-instructies meestal door de JVM gebruikt om niet- vergrendelde vergrendeling te implementeren. Als de JVM kan zien dat een slot momenteel niet is vergrendeld, probeert het een CAS te gebruiken om het slot te verkrijgen. Als het CAS slaagt, is het niet nodig om de dure threadplanning, contextomschakeling, enzovoort te doen. Zie Bias Locking in HotSpot voor meer informatie over de gebruikte technieken.