Java Academy · Lektion

Trådsäkerhet och sidoeffekter

Undvik delat föränderliga tillstånd.

Lektion 3 av 413 steg

Trådsäkerhet och sidoeffekter är en gratis lektion i Java Academy på CoddyKit. Detta är lektion 3 av 4. Ni kan läsa hela lektionen gratis nedan och sedan öva praktiskt i webbläsaren med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt. Den ingår i lärvägen för Java Academy, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Java Academy innehåller totalt 4 lektioner.

Parallellism innebär samtidiga åtkomster

I en parallell ström kör flera trådar dina lambdauttryck samtidigt. Allt delat muterbart tillstånd som de berör blir en race condition.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        int safe = IntStream.rangeClosed(1, 1000).parallel().sum();
        System.out.println(safe);
    }
}

Faran: ändra en delad variabel

En vanlig int-räknare som används av flera trådar tappar uppdateringar. Ackumulera aldrig i delat, muterbart tillstånd inuti forEach.

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger();
        IntStream.rangeClosed(1, 1000).parallel()
            .forEach(n -> counter.incrementAndGet());
        System.out.println(counter.get());
    }
}

Varför vanliga samlingar går sönder

Att lägga till i en trådosäker ArrayList från en parallell forEach kan skada samlingen eller göra att element försvinner. Lösningen är att inte dela och ändra data över huvud taget.

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        List<Integer> result = IntStream.rangeClosed(1, 10).parallel()
            .boxed()
            .collect(Collectors.toList());
        System.out.println(result.size());
    }
}

Rätt verktyg: collect

Använd collect i stället för forEach med sidoeffekter. Samlarens ramverk ackumulerar säkert delbehållare per tråd och sammanfogar dem.

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        List<Integer> squares = IntStream.rangeClosed(1, 6).parallel()
            .map(n -> n * n)
            .boxed()
            .collect(Collectors.toList());
        System.out.println(squares);
    }
}

Rätt verktyg: reduce

reduce är trådsäkert av naturen när operationen är associativ och tillståndslös, eftersom delresultat sammanfogas utan delade ändringar.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        int sum = IntStream.rangeClosed(1, 1000).parallel()
            .reduce(0, Integer::sum);
        System.out.println(sum);
    }
}

Tillståndslösa lambdauttryck

Lambdauttryck i strömmar bör vara tillståndslösa: resultatet ska bara bero på indata, inte på externa muterbara variabler eller tidigare element.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long count = IntStream.rangeClosed(1, 1000).parallel()
            .filter(n -> n % 2 == 0)
            .count();
        System.out.println(count);
    }
}

Atomära typer som reservlösning

Om du verkligen måste dela en räknare använder du atomära klasser som AtomicLong. De garanterar korrekta uppdateringar, även om de medför konkurrens mellan trådarna.

import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        AtomicLong sum = new AtomicLong();
        IntStream.rangeClosed(1, 1000).parallel()
            .forEach(sum::addAndGet);
        System.out.println(sum.get());
    }
}

Samtidiga samlingar

När du behöver en map som delas mellan trådar är en ConcurrentHashMap säker. Föredra ändå Collectors.groupingByConcurrent när det är möjligt.

import java.util.Map;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> words = List.of("a", "bb", "cc", "ddd");
        Map<Integer, List<String>> byLen = words.parallelStream()
            .collect(Collectors.groupingByConcurrent(String::length));
        System.out.println(byLen);
    }
}

Undvik ordningsberoende logik

Förlita dig inte på den ordning elementen påträffas i inuti parallella lambdauttryck. Operationer som förutsätter sekventiell bearbetning ger fel resultat parallellt.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        int sum = IntStream.rangeClosed(1, 100).parallel()
            .map(n -> n * 2)
            .sum();
        System.out.println(sum);
    }
}

Rena funktioner parallelliseras säkert

Den gyllene regeln är att skriva rena, tillståndslösa och icke-interfererande operationer. Då kan en ström köras sekventiellt eller parallellt och ändå ge identiska, korrekta resultat.

import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> in = List.of("red", "green", "blue");
        String out = in.parallelStream()
            .map(String::toUpperCase)
            .sorted()
            .collect(Collectors.joining(","));
        System.out.println(out);
    }
}

Icke-interferens

Ändra inte källsamlingen medan en ström bearbetar den. Det orsakar ConcurrentModificationException eller odefinierat beteende, både sekventiellt och parallellt.

import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> src = List.of(1, 2, 3, 4);
        List<Integer> doubled = src.parallelStream()
            .map(n -> n * 2)
            .collect(Collectors.toList());
        System.out.println(doubled);
    }
}

Snabb kontroll

Vilket är det rekommenderade sättet att ackumulera resultat från en parallell ström?

Sammanfattning

Du såg till att parallella strömmar förblir korrekta:

  • Delat muterbart tillstånd i parallella lambdauttryck orsakar race conditions.
  • Föredra collect och reduce framför forEach med sidoeffekter.
  • Håll lambdauttrycken tillståndslösa, rena och icke-interfererande.
  • Om du måste dela tillstånd använder du atomära eller samtidiga samlingar, men det är en sista utväg.
Gratis att börja

Lär dig Java med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
104
Lektioner
374

Vanliga frågor

Är lektionen ”Trådsäkerhet och sidoeffekter” gratis?

Ja – hela texten till ”Trådsäkerhet och sidoeffekter” kan läsas gratis här på webben. Om Ni vill öva interaktivt med en inbyggd kodredigerare och en AI-handledare som är tillgänglig dygnet runt och låsa upp resten av kursen i Java Academy, kan Ni uppgradera till CoddyKit PRO. Kursen i Java Academy innehåller totalt 4 lektioner.

Vad lär jag mig i ”Trådsäkerhet och sidoeffekter”?

Undvik delat föränderliga tillstånd. Ni övar på Java Academy med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Java Academy?

Du behöver inga förkunskaper. Utbildningen i Java Academy på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.

Hur lång tid tar lektionen ”Trådsäkerhet och sidoeffekter”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Java Academy-lektionen?

Ja. Varje Java Academy-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Skapa parallella strömmar
  2. När parallellism hjälper
  3. Trådsäkerhet och sidoeffekter
  4. Vanliga fallgropar
← Tillbaka till Java Academy