Trådsikkerhed og sideeffekter
Undgå delt, muterbar tilstand
Trådsikkerhed og sideeffekter er en gratis Java Academy-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i Java Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Java Academy-kurset indeholder 4 lektioner i alt.
Parallel behandling betyder samtidig adgang
I en parallel stream kører flere tråde dine lambdaer på samme tid. Enhver delt foranderlig tilstand, de berører, bliver til 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);
}
}Faren ved at ændre en delt variabel
En almindelig int-tæller, som berøres af mange tråde, mister opdateringer. Du bør aldrig samle resultater i en delt foranderlig tilstand inde i 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());
}
}Hvorfor almindelige samlinger går i stykker
Hvis du føjer elementer til en ikke-trådsikker ArrayList fra en parallel forEach, kan samlingen blive ødelagt, eller elementer kan gå tabt. Løsningen er slet ikke at dele og ændre den.
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());
}
}Det rigtige værktøj: collect
Brug collect i stedet for forEach med sideeffekter. Indsamlingsrammeværket samler sikkert delvise beholdere pr. tråd og fletter 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);
}
}Det rigtige værktøj: reduce
reduce er i sig selv trådsikker, når operationen er associativ og tilstandsløs, fordi den fletter delresultater uden delt ændring af tilstand.
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);
}
}Tilstandsløse lambdaer
Lambdaer i streams bør være tilstandsløse: Deres resultat afhænger kun af inddata og ikke af eksterne foranderlige variable eller tidligere elementer.
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);
}
}Atomare typer som udvej
Hvis du virkelig er nødt til at dele en tæller, kan du bruge atomare klasser som AtomicLong. De serialiserer opdateringer korrekt, selv om de medfører mere konkurrence om adgangen.
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());
}
}Samtidige samlinger
Når du har brug for et delt map på tværs af tråde, er en ConcurrentHashMap sikker. Foretræk stadig Collectors.groupingByConcurrent, hvor det er muligt.
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);
}
}Undgå rækkefølgeafhængig logik
Du bør ikke stole på møderækkefølgen inde i parallelle lambdaer. Operationer, der forudsætter sekventiel behandling, giver forkerte resultater parallelt.
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);
}
}Rene funktioner kan paralleliseres sikkert
Den gyldne regel er at skrive rene, tilstandsløse og ikke-forstyrrende operationer. Så kan en stream være sekventiel eller parallel og stadig give identiske, korrekte resultater.
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);
}
}Ingen indblanding
Du bør ikke ændre kildekollektionen, mens en stream kører over den. Det medfører ConcurrentModificationException eller udefineret adfærd, både sekventielt og parallelt.
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);
}
}Hurtigt tjek
Hvad er den anbefalede måde at samle resultater fra en parallel stream på?
Opsummering
Du har holdt parallelle streams korrekte:
- Delt foranderlig tilstand i parallelle lambdaer medfører kapløbstilstande.
- Foretræk
collectogreducefrem forforEachmed sideeffekter. - Hold lambdaer tilstandsløse, rene og ikke-forstyrrende.
- Hvis du er nødt til at dele tilstand, kan du bruge atomare eller samtidige samlinger, men det bør være sidste udvej.
Lær Java med en AI-underviser — gratis
Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.
- Kurser
- 104
- Lektioner
- 374
Ofte stillede spørgsmål
Er lektionen “Trådsikkerhed og sideeffekter” gratis?
Ja — hele teksten til “Trådsikkerhed og sideeffekter” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af Java Academy-kurset, skal du opgradere til CoddyKit PRO. Java Academy-kurset indeholder 4 lektioner i alt.
Hvad lærer jeg i “Trådsikkerhed og sideeffekter”?
Undgå delt, muterbar tilstand Du øver dig i Java Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.
Skal jeg have erfaring for at begynde på Java Academy?
Der kræves ingen tidligere erfaring. Java Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.
Hvor lang tid tager lektionen “Trådsikkerhed og sideeffekter”?
De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.
Kan jeg skrive og køre kode i denne Java Academy-lektion?
Ja. Alle Java Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.
Alle lektioner i dette kursus
- Oprettelse af parallelle streams
- Hvornår parallelitet hjælper
- Trådsikkerhed og sideeffekter
- Almindelige faldgruber