Eine Geschichte, bei der Sie bei der parallelen Ausführung mit Java auf Thread-Sicherheit achten müssen

Einführung

Bei der Arbeit erhielt ich die folgende Beratung. "Wenn die Parallelverarbeitung mit Completable Future durchgeführt wird, wird der Wert möglicherweise nicht alle zehn bis hundert Mal korrekt zurückgegeben."

Notieren Sie sich die Antwort zu diesem Zeitpunkt.

Problem

Die Ausführungsumgebung ist

ist.

Es gibt die folgenden Prozesse. Informationen werden mithilfe von CompletableFuture parallel in summaryItem gespeichert. xxxAPI.search (number); ruft eine externe API auf, um Artikelinformationen abzurufen. Wo lauern die Käfer?

  public List<Item> search(int length) {
    //Variablen, die Suchergebnisse enthalten
    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(() -> {
        //Rufen Sie Daten ab, indem Sie eine externe API aufrufen
        Item item = xxxAPI.search(number);
        summaryItem.add(item);
      }, pool);
      futures.add(f);
    }

    //Warten Sie, bis die gesamte Parallelverarbeitung abgeschlossen ist
    CompletableFuture<Void> all =
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[length]));
    all.join();

    return summaryItem;
  }

Antworten

Dies ist Teil der folgenden Verarbeitung.

        summaryItem.add(item);

summaryItem ist java.util.ArrayList. ArrayList ist nicht threadsicher. ** ** ** Werfen wir einen Blick auf JavaDoc.

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

** Diese Implementierung ist nicht synchronisiert. ** Wenn mehrere Threads parallel auf eine ArrayList-Instanz zugreifen und mindestens einer dieser Threads die Liste strukturell ändert, muss sie extern synchronisiert werden. Strukturelle Änderungen sind das Hinzufügen oder Entfernen eines oder mehrerer Elemente oder das explizite Ändern der Größe des zugrunde liegenden Arrays. Das Festlegen nur des Werts eines Elements ist keine strukturelle Änderung. Dies wird normalerweise durch Synchronisieren mit einigen Objekten erreicht, die die Liste auf natürliche Weise kapseln. Wenn ein solches Objekt nicht vorhanden ist, müssen Sie die Methode Collections.synchronizedList verwenden, um die Liste zu "umbrechen". Es wird empfohlen, dies zum Zeitpunkt der Erstellung zu tun, um zu verhindern, dass versehentlich auf die Liste zugegriffen wird, ohne synchronisiert zu werden.

Du hast es fest geschrieben.

Korrespondenz

Verwenden Sie eine thread-sichere Liste. Oder wechseln Sie zunächst zur thread-sicheren Verarbeitung. Wir antworteten mit einem Ansatz in Form von.

Verwenden Sie eine thread-sichere Liste

Das Paket "java.util.concurrent" hat eine gut synchronisierte Liste. Lassen Sie uns dies verwenden.

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

Ändern Sie einfach den obigen Code wie unten und es wird normal funktionieren.

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

In einigen Fällen löst die Verwendung von "Collections.synchronizedList" das Problem. Wenn Sie jedoch einen Iterator verwenden, müssen Sie sich trotzdem selbst synchronisieren.

Wechseln Sie zur thread-sicheren Verarbeitung

Zunächst sollten Sie den Wert im Format java.util.function.Supplier zurückgeben.

  public List<Item> search(int length) {
    //Paralleler Ausführungsprozess der Suche
    List<CompletableFuture<Item>> futures = new ArrayList<>(length);
    for (int i = 0; i < length; i++) {
      final int number = i;
      CompletableFuture<Item> f = CompletableFuture.supplyAsync(() -> {
        //Rufen Sie Daten ab, indem Sie eine externe API aufrufen
        return xxxAPI.search(number);
      }, pool);
      futures.add(f);
    }

    //Sammlung von Suchergebnissen
    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);//Ich habe es angemessen geschrieben.
    }
  }

Es passiert sehr zufällig und der Punkt ist, dass es schwierig ist, ein Problem zu finden, ohne die Java-API zu kennen. Der Testcode wird auch (grob) durchlaufen.

Ich denke, es ist nützlicher, eine Codeüberprüfung für diese Teile durchzuführen, als zu debuggen.

Was ich mehr machen will

Sie können es wahrscheinlich nicht mit CheckStyle finden, aber ich glaube, Sie können das Problem mit einem statischen Code-Analyse-Tool finden. Ich werde in Zukunft danach suchen.

Recommended Posts

Eine Geschichte, bei der Sie bei der parallelen Ausführung mit Java auf Thread-Sicherheit achten müssen
Ein Hinweis, wenn ich süchtig danach bin, Docker Hub Vault im Servermodus zu verwenden
Zwei Möglichkeiten, einen Thread in Java + @ zu starten
Zusammenfassung der Verwendung des im IE festgelegten Proxy-Sets bei der Verbindung mit Java
Code, der verwendet werden soll, wenn Sie Json nur mit Standardbibliotheken in Java verarbeiten möchten
Aufrufen von Funktionen in großen Mengen mit Java Reflection
Machen Sie "Ich bin kein Roboter" in Java EE (Jakarta EE)
Beachten Sie das Multithread-Problem, wenn Sie mit Java Servlet arbeiten
Adressiert, da Azure-Funktionen in Java nicht mehr funktionieren
Wenn Sie Annotation in Java 8 dynamisch ersetzen möchten
Wenn Sie Java-Bibliothekstests mit Spock in mehreren Modulen mit Gradle in Android Studio 3 implementieren möchten