web-dev-qa-db-fra.com

Java flux parallèle: comment attendre la fin d'un flux parallèle pour les threads?

J'ai donc une liste à partir de laquelle j'obtiens un flux parallèle pour remplir une carte, comme suit:

Map<Integer, TreeNode> map = new HashMap<>();
List<NodeData> list = some_filled_list;

//Putting data from the list into the map
list.parallelStream().forEach(d -> {
                TreeNode node = new TreeNode(d);
                map.put(node.getId(), node);
            });

//print out map
map.entrySet().stream().forEach(entry -> {
     System.out.println("Processing node with ID = " + entry.getValue().getId());
                });

Le problème avec ce code est que la carte est imprimée lorsque le processus de "mise en données" est toujours en cours (car c'est parallèle), par conséquent, la carte n'a pas encore reçu tous les éléments de la liste. Bien sûr, dans mon vrai code, il ne s'agit pas seulement d'imprimer la carte; J'utilise une carte pour profiter de O(1) temps de recherche.

Ma question est:

  1. comment faire attendre le thread principal pour que la "mise en données" soit terminée avant l'impression de la carte? J'ai essayé de mettre le "mettre des données" dans un thread t, et faire t.start() et t.join(), mais cela n'aide pas.

  2. Peut-être que je ne suis pas censé utiliser le flux parallèle dans ce cas? La liste est longue, et je veux juste profiter du parallélisme pour améliorer l'efficacité.

10
Simo

Avec cette list.parallelStream().forEach vous violez le side-effects propriété explicitement indiquée dans la documentation Stream.

Aussi quand vous dites ce code est que la carte est imprimée lorsque le processus de "mise en données" est toujours en cours (car c'est parallèle), ce n'est pas vrai, car forEach est une opération de terminal et attendra d'être terminée, jusqu'à ce qu'elle puisse passer à un processus la ligne suivante. Vous pourriez être voyant en tant que tel, car vous collectez vers un thread-safe HashMap et certaines entrées peuvent ne pas être dans cette carte ... Pensez à une autre manière, à quoi se produirait-il si vous mettiez plusieurs entrées de plusieurs threads dans un HashMap? Eh bien, beaucoup de choses peuvent se casser, comme des entrées manquantes, sur une carte incorrecte ou incohérente, etc.

Bien sûr, le changer en ConcurrentHashMap fonctionnerait, car il est compatible avec les threads, mais vous violez toujours la propriété des effets secondaires, bien que de manière "sûre".

La bonne chose à faire est de collect vers un Map directement sans forEach:

Map<Integer, TreeNode> map = list.parallelStream()
        .collect(Collectors.toMap(
                NodeData::getId,
                TreeNode::new
        ));

De cette façon, même pour un traitement parallèle, tout irait bien. Notez juste que vous auriez besoin de lots (dizaines de milliers d'éléments) pour avoir une augmentation mesurable des performances du traitement parallèle.

13
Eugene

Les opérations de flux seront bloquées jusqu'à ce qu'elles soient terminées pour les implémentations parallèles et non parallèles.

Donc ce que vous voyez n'est pas the "putting data" process is still going on - il s'agit très probablement d'une corruption de données, car HashMap n'est pas threadsafe. Essayez d'utiliser ConcurrentHashMap à la place.

2
Stadnyk Oleksii

Je suppose que s'il est possible que le flux soit toujours en cours de traitement, vous pouvez essayer quelque chose comme:

    List<NodeData> list = new ArrayList<>();

    //Putting data from the list into the map
    Map<Integer, TreeNode> map = list.parallelStream()
            .collect(Collectors.toMap(
                    n -> n.getId(),
                    n -> new TreeNode(n)
            ));

Au moins maintenant, vous avez un terminal sur le flux. Vous utiliserez plusieurs threads possibles et le mappage sera certainement terminé.

1
OldCurmudgeon