Il y a une excellente explication de ce problème par Andrei Pangin , datée du 07 avril 2015. Elle est disponible ici , mais elle est écrite en russe (je suggère quand même de revoir les échantillons de code - ils sont internationaux). Le problème général est un verrou lors de l'initialisation de la classe.
Voici quelques citations de l'article:
Selon JLS , chaque classe possède un verrou d'initialisation unique qui est capturé lors de l'initialisation. Lorsqu'un autre thread tente d'accéder à cette classe pendant l'initialisation, il sera bloqué sur le verrou jusqu'à ce que l'initialisation soit terminée. Lorsque les classes sont initialisées simultanément, il est possible d'obtenir un blocage.
J'ai écrit un programme simple qui calcule la somme des nombres entiers, que doit-il imprimer?
public class StreamSum {
static final int SUM = IntStream.range(0, 100).parallel().reduce((n, m) -> n + m).getAsInt();
public static void main(String[] args) {
System.out.println(SUM);
}
}
Supprimer maintenant parallel()
ou remplacez lambda par Integer::sum
call - qu'est-ce qui va changer?
Ici, nous voyons à nouveau des blocages [il y avait quelques exemples de blocages dans les initialiseurs de classe précédemment dans l'article]. En raison des parallel()
opérations de flux s'exécutent dans un pool de threads distinct. Ces threads essaient d'exécuter le corps lambda, qui est écrit en bytecode en tant que private static
méthode à l'intérieur de la StreamSum
classe. Mais cette méthode ne peut pas être exécutée avant la fin de l'initialiseur statique de classe, qui attend les résultats de la fin du flux.
Quoi de plus époustouflant: ce code fonctionne différemment dans différents environnements. Il fonctionnera correctement sur une seule machine à processeur et sera probablement suspendu à une machine à plusieurs processeurs. Cette différence vient de l'implémentation du pool Fork-Join. Vous pouvez le vérifier vous-même en modifiant le paramètre-Djava.util.concurrent.ForkJoinPool.common.parallelism=N