Une histoire dans laquelle vous devez faire attention à la sécurité des threads lors de l'exécution en parallèle avec Java

introduction

Au travail, j'ai reçu la consultation suivante. "Lorsqu'un traitement parallèle est effectué à l'aide de Completable Future, la valeur peut ne pas être renvoyée correctement environ une fois toutes les dizaines à centaines de fois."

Prenez note de la réponse à ce moment-là.

problème

L'environnement d'exécution est

est.

Il existe les processus suivants. Les informations sont stockées dans summaryItem en parallèle à l'aide de CompletableFuture. xxxAPI.search (nombre); appelle une API externe pour obtenir des informations sur l'élément. Où se cachent les insectes?

  public List<Item> search(int length) {
    //Variables contenant les résultats de la recherche
    List<Item> summaryItem = new ArrayList<>(length);

    List<CompletableFuture<Void>> futures = new ArrayList<>(length);
    for (int i = 0; i < length; i++) {
      final int number = i;
      CompletableFuture<Void> f = CompletableFuture.runAsync(() -> {
        //Obtenez des données en appelant une API externe
        Item item = xxxAPI.search(number);
        summaryItem.add(item);
      }, pool);
      futures.add(f);
    }

    //Attendez que tous les traitements parallèles soient terminés
    CompletableFuture<Void> all =
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[length]));
    all.join();

    return summaryItem;
  }

Répondre

C'est la partie du traitement suivant.

        summaryItem.add(item);

summaryItem est java.util.ArrayList. ArrayList n'est pas ** thread-safe. ** ** Jetons un coup d'œil à JavaDoc.

https://docs.oracle.com/javase/jp/8/docs/api/java/util/ArrayList.html

** Cette implémentation n'est pas synchronisée. ** Si plusieurs threads accèdent à une instance ArrayList en parallèle et qu'au moins un de ces threads modifie structurellement la liste, elle doit être synchronisée en externe. Les modifications structurelles sont le processus d'ajout ou de suppression d'un ou plusieurs éléments, ou le redimensionnement explicite du tableau sous-jacent. Le processus consistant à définir uniquement la valeur d'un élément n'est pas un changement structurel. Ceci est généralement réalisé en synchronisant avec certains objets qui encapsulent naturellement la liste. Si un tel objet n'existe pas, essayez de «envelopper» la liste à l'aide de la méthode Collections.synchronizedList. Il est recommandé de le faire au moment de la création pour éviter tout accès accidentel à la liste sans être synchronisée.

Vous l'avez écrit fermement.

Correspondance

Utilisez une liste thread-safe. Ou, passez en premier lieu au traitement thread-safe. Nous avons répondu avec une approche sous la forme de.

Utiliser une liste thread-safe

Le paquet java.util.concurrent a une liste bien synchronisée. Utilisons ceci.

List<Item> summaryItem = new ArrayList<>(length);

Changez simplement le code ci-dessus comme ci-dessous et cela fonctionnera normalement.

List<Item> summaryItem = new CopyOnWriteArrayList<>(length);

Dans certains cas, l'utilisation de Collections.synchronizedList résoudra le problème, mais si vous utilisez iterator, vous devrez toujours vous synchroniser.

Passer au traitement thread-safe

En premier lieu, vous devez renvoyer la valeur au format java.util.function.Supplier.

  public List<Item> search(int length) {
    //Processus d'exécution parallèle de la recherche
    List<CompletableFuture<Item>> futures = new ArrayList<>(length);
    for (int i = 0; i < length; i++) {
      final int number = i;
      CompletableFuture<Item> f = CompletableFuture.supplyAsync(() -> {
        //Obtenez des données en appelant une API externe
        return xxxAPI.search(number);
      }, pool);
      futures.add(f);
    }

    //Collection de résultats de recherche
    try {
      List<Item> summaryItem = new ArrayList<>();
      for (CompletableFuture<Item> f : futures) {
        summaryItem.add(f.get());
      }
      return summaryItem;
    } catch (ExecutionException | InterruptedException ex) {
      throw new RuntimeException(ex);//Je l'ai écrit de manière appropriée.
    }
  }

Cela se produit extrêmement accidentellement, et le fait est qu'il est difficile de trouver un problème sans une certaine connaissance de l'API Java. Le code de test passera également (approximativement).

Je pense qu'il est plus utile de faire une revue de code pour ces parties que de déboguer.

Ce que je veux faire plus

Vous ne pouvez probablement pas le trouver avec CheckStyle, mais j'ai l'impression que vous pouvez trouver le problème avec un outil d'analyse de code statique. Je le chercherai dans le futur.

Recommended Posts

Une histoire dans laquelle vous devez faire attention à la sécurité des threads lors de l'exécution en parallèle avec Java
Une note quand je suis accro à l'utilisation de Docker Hub Vault en mode serveur
Deux façons de démarrer un thread en Java + @
Résumé de l'utilisation du jeu de proxy dans IE lors de la connexion avec Java
Code à utiliser lorsque vous souhaitez traiter Json en Java avec uniquement des bibliothèques standard
Comment appeler des fonctions en bloc avec la réflexion Java
Créez "Je ne suis pas un robot" en Java EE (Jakarta EE)
Remarquez un problème multi-thread lorsque vous travaillez avec Java Servlet
Résolution du problème lorsque Azure Functions a cessé de fonctionner en Java
Lorsque vous souhaitez remplacer dynamiquement l'annotation dans Java 8
Lorsque vous souhaitez implémenter des tests de bibliothèque Java avec Spock en multi-module avec Gradle dans Android Studio 3