Trådsäkerhet och sidoeffekter
Undvik delat föränderliga tillstånd.
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
collectochreduceframförforEachmed 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.
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
- Skapa parallella strömmar
- När parallellism hjälper
- Trådsäkerhet och sidoeffekter
- Vanliga fallgropar