Sök…


Introduktion

Java Atomic Types är enkla muterbara typer som tillhandahåller grundläggande operationer som är trådsäkra och atomiska utan att ta till låsning. De är avsedda att användas i fall där låsning skulle vara en samtidigt flaskhals, eller där det finns risk för dödlås eller boskap.

parametrar

Parameter Beskrivning
uppsättning Flyktig uppsättning av fältet
skaffa sig Flyktig läsning av fältet
lazySet Detta är en butik beställd drift av fältet
compareAndSet Om värdet är det utskickade värdet skickas det till det nya värdet
getAndSet få det aktuella värdet och uppdatera

Anmärkningar

Många på i huvudsak kombinationer av flyktiga läsningar eller skrivningar och CAS- operationer. Det bästa sättet att förstå detta är att titta på källkoden direkt. Exempelvis AtomicInteger , Unsafe.getAndSet

Skapa atomtyper

För enkel multigängad kod är det acceptabelt att använda synkronisering . Men att använda synkronisering påverkar livligheten och när kodbasen blir mer komplex ökar sannolikheten att du kommer att hamna med Deadlock , Starvation eller Livelock .

I fall av mer komplex samtidighet är Atomic Variables ofta ett bättre alternativ, eftersom det gör det möjligt att få åtkomst till en individuell variabel på ett gängsäkert sätt utan att använda sig av synkroniserade metoder eller kodblock.

Skapa en AtomicInteger typ:

AtomicInteger aInt = new AtomicInteger() // Create with default value 0

AtomicInteger aInt = new AtomicInteger(1) // Create with initial value 1

På liknande sätt för andra instanstyper.

AtomicIntegerArray aIntArray = new AtomicIntegerArray(10) // Create array of specific length
AtomicIntegerArray aIntArray = new AtomicIntegerArray(new int[] {1, 2, 3}) // Initialize array with another array

På liknande sätt för andra atomtyper.

Det finns ett anmärkningsvärt undantag att det inte finns några float och double . Dessa kan simuleras genom användning av Float.floatToIntBits(float) och Float.intBitsToFloat(int) för float samt Double.doubleToLongBits(double) och Double.longBitsToDouble(long) för dubbel.

Om du är villig att använda sun.misc.Unsafe du använda valfri primitiv variabel som atom genom att använda atomoperationen i sun.misc.Unsafe . Alla primitiva typer bör konverteras eller kodas i int eller länge för att använda det på detta sätt. För mer om detta se: sun.misc.Unsafe .

Motivation för atomtyper

Det enkla sättet att implementera flertrådiga applikationer är att använda Java: s inbyggda synkroniserings- och låsningsprimitiv; t.ex. det synchronized nyckelordet. Följande exempel visar hur vi kan använda synchronized att samla räkningar.

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;
    }
}

Denna implementering fungerar korrekt. Men om du har ett stort antal trådar som gör massor av samtidiga samtal på samma Counters objekt kan synkroniseringen vara en flaskhals. Specifikt:

  1. Varje synchronized metodsamtal börjar med att den aktuella tråden förvärvar låset för Counters instansen.
  2. Tråden kommer att hålla låset medan den kontrollerar number värde och uppdaterar räknaren.
  3. Slutligen kommer det att släppa låset, så att andra trådar får åtkomst.

Om en tråd försöker skaffa låset medan en annan håller fast det kommer den försökta tråden att blockeras (stoppas) i steg 1 tills låset lossas. Om flera trådar väntar kommer en av dem att få det, och de andra kommer att fortsätta att blockeras.

Detta kan leda till ett par problem:

  • Om det finns mycket strid för låset (dvs. massor av tråd försök att skaffa det), kan vissa trådar blockeras under lång tid.

  • När en tråd är blockerad och väntar på låset försöker operativsystemet vanligtvis byta exekvering till en annan tråd. Denna sammanhangsbytning medför relativt stor prestanda på processorn.

  • När det finns flera trådar blockerade på samma lås, finns det inga garantier för att någon av dem kommer att behandlas "rättvist" (det vill säga att varje tråd garanteras att planeras att köras). Detta kan leda till gänghunga .

Hur implementerar man Atomtyper?

Låt oss börja med att skriva om exemplet ovan med hjälp av AtomicInteger räknare:

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;
    }
}

Vi har ersatt int[] med ett AtomicInteger[] och initialiserat det med en instans i varje element. Vi har också lagt till samtal till incrementAndGet() och get() i stället för operationer på int värden.

Men det viktigaste är att vi kan ta bort det synchronized nyckelordet eftersom det inte längre behövs låsning. Detta fungerar eftersom incrementAndGet() och get() -operationerna är atomära och gängsäkra . I detta sammanhang betyder det att:

  • Varje räknare i matrisen kommer endast att observeras i antingen "före" -tillståndet för en operation (som ett "inkrement") eller i "efter" -tillståndet.

  • Antagande att operationen sker vid tidpunkten T , kommer ingen tråd att kunna se "före" -läget efter tiden T

Även om två trådar faktiskt kan försöka uppdatera samma AtomicInteger instans samtidigt, säkerställer implementeringarna av operationerna att endast ett steg sker i taget på den givna instansen. Detta görs utan låsning, vilket ofta resulterar i bättre prestanda.

Hur fungerar atomtyper?

Atomtyper förlitar sig vanligtvis på specialiserade maskinvaruinstruktioner i instruktionsuppsättningen för målmaskinen. Exempelvis tillhandahåller Intel-baserade instruktionsuppsättningar en CAS ( jämför och byt ) -instruktion som kommer att utföra en specifik sekvens av minnesoperationer atomiskt.

Dessa lågnivåinstruktioner används för att implementera operationer på högre nivå i API: erna för respektive AtomicXxx klasser. Till exempel (igen, i C-liknande pseudokod):

private volatile num;

int increment() {
  while (TRUE) {
    int old = num;
    int new = old + 1;
    if (old == compare_and_swap(&num, old, new)) {
      return new;
    }
  }
}

Om det inte finns någon stridighet på AtomicXxxx kommer if testet att lyckas och slingan avslutas omedelbart. Om det finns strid, kommer if att misslyckas för alla utom en av trådarna, och de "snurrar" i slingan under ett litet antal cykler i slingan. I praktiken är snurrningen större ordningsföljder snabbare (utom vid orealistiskt höga stridighetsnivåer, där synkroniserad fungerar bättre än atomklasser, eftersom när CAS-operationen misslyckas, kommer försöket bara att lägga till mer strid) än att stoppa tråden och byta till en annan ett.

Förresten, CAS-instruktioner används vanligtvis av JVM för att implementera obestämd låsning . Om JVM kan se att ett lås för närvarande inte är låst, kommer det att försöka använda en CAS för att förvärva låset. Om CAS lyckas, finns det inget behov av att göra den dyra trådplaneringen, kontextväxling och så vidare. För mer information om de använda teknikerna, se Partisk låsning i HotSpot .



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow