Sûreté vis-à-vis des threads et effets secondaires
Évitez l’état mutable partagé
Sûreté vis-à-vis des threads et effets secondaires est une leçon Java Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Java Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Java Academy comprend 4 leçons au total.
Le parallel implique des accès concurrents
Dans un flux parallel, plusieurs fils d’exécution exécutent vos lambdas en même temps. Tout état mutable partagé auquel elles accèdent devient une condition de concurrence.
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);
}
}Le danger : modifier une variable partagée
Un compteur int ordinaire auquel plusieurs fils d’exécution accèdent perd des mises à jour. N’accumulez jamais de résultats dans un état mutable partagé à l’intérieur de 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());
}
}Pourquoi les collections ordinaires échouent
Ajouter des éléments à un ArrayList non sûr vis-à-vis des fils d’exécution depuis un forEach parallel peut le corrompre ou faire disparaître des éléments. La solution consiste à ne pas partager ni modifier cet état.
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());
}
}L’outil approprié : collect
Utilisez collect plutôt que forEach avec des effets de bord. L’infrastructure des collecteurs accumule de manière sûre des conteneurs partiels pour chaque fil d’exécution, puis les fusionne.
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);
}
}L’outil approprié : reduce
reduce est intrinsèquement sûr vis-à-vis des fils d’exécution lorsque l’opération est associative et sans état, car il fusionne les résultats partiels sans modification partagée.
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);
}
}Lambdas sans état
Les lambdas utilisées dans les flux doivent être sans état : leur résultat dépend uniquement de l’entrée, et non de variables externes mutables ou d’éléments précédents.
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);
}
}Les types atomiques comme solution de secours
Si vous devez réellement partager un compteur, utilisez des classes atomiques comme AtomicLong. Elles sérialisent correctement les mises à jour, mais ajoutent de la contention.
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());
}
}Collections concurrentes
Lorsque vous avez besoin d’une map partagée entre plusieurs fils d’exécution, une ConcurrentHashMap est sûre. Toutefois, privilégiez Collectors.groupingByConcurrent lorsque cela est possible.
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);
}
}Éviter la logique dépendante de l’ordre
Ne vous fiez pas à l’ordre de rencontre dans les lambdas parallel. Les opérations qui supposent un traitement séquentiel produisent des résultats incorrects en parallel.
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);
}
}Les fonctions pures se parallélisent en toute sécurité
La règle d’or consiste à écrire des opérations pures, sans état et non interférentes. Un flux peut alors être séquentiel ou parallel et produire des résultats identiques et corrects.
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);
}
}Absence d’interférence
Ne modifiez pas la collection source pendant qu’un flux la parcourt. Cela provoque une ConcurrentModificationException ou un comportement indéfini, en mode séquentiel comme en parallel.
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);
}
}Vérification rapide
Quelle est la manière recommandée d’accumuler les résultats d’un flux parallel ?
Récapitulatif
Vous avez préservé la correction des flux parallel :
- L’état mutable partagé dans les lambdas parallel provoque des conditions de concurrence.
- Privilégiez
collectetreduceàforEachavec des effets de bord. - Gardez les lambdas sans état, pures et non interférentes.
- Si le partage est indispensable, utilisez des types atomiques ou des collections concurrentes, mais uniquement en dernier recours.
Questions Fréquemment Posées
La leçon « Sûreté vis-à-vis des threads et effets secondaires » est-elle gratuite ?
Oui — le texte complet de « Sûreté vis-à-vis des threads et effets secondaires » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Java Academy, passe à CoddyKit PRO. Le cours Java Academy comprend 4 leçons au total.
Qu'est-ce que j'apprendrai dans « Sûreté vis-à-vis des threads et effets secondaires » ?
Évitez l’état mutable partagé Tu pratiques Java Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Java Academy ?
Aucune expérience préalable n'est requise. Java Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.
Combien de temps prend la leçon « Sûreté vis-à-vis des threads et effets secondaires » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Java Academy ?
Oui. Chaque leçon Java Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Créer des flux parallèles
- Quand le parallélisme est utile
- Sûreté vis-à-vis des threads et effets secondaires
- Pièges courants