2017-01-18 2 views
0

Ich habe ein merkwürdiges Verhalten einer Befehlszeilenanwendung beobachtet, die SwingWorkers aufruft. Der Code ist nicht optimal in dem Sinne, dass er viele Thread-Pools erstellt. Aufgrund der Kontrolle der Variablen generation führen alle diese Pools außer dem letzten jedoch keinen Code aus. Das bedeutet, dass Threads aus diesen Pools nicht nur an Rennen für die Sperren teilnehmen, sondern auch Müll gesammelt und gelöscht werden müssen.SwingWorker.process() wird nicht in einer Befehlszeilenanwendung aufgerufen

Ein minimales Arbeitsbeispiel (nicht zu machen, etwas Sinnvolles) ist die folgend:

package test; 

import java.util.List; 
import java.util.concurrent.ExecutorService; 
import java.util.concurrent.Executors; 
import javax.swing.SwingWorker; 

public class Tester { 

private final int threads; 
private ExecutorService threadPool; 
private final int size; 
private long currGen; 
private int left; 
private int done; 

public Tester(int size, int threads) { 
    this.threads = threads; 

    this.size = size; 

    this.currGen = 0; 

    this.left = size; 
    this.done = 0; 
} 

private class TestWorker extends SwingWorker<Object, Object> { 
    private final Tester tester; 
    private final long generation; 

    TestWorker(Tester tester, long generation) { 
     this.tester = tester; 
     this.generation = generation; 
    } 

    @Override 
    protected Object doInBackground() throws Exception { 
     while(this.tester.get(generation)) { 
      Thread.sleep(1); 
      publish(1); 
     } 

     return null; 
    } 

    @Override 
    protected void process(List<Object> results) { 
     for(Object n : results) { 
      this.tester.put(generation); 
     } 
    } 
} 

public void run() {  
    this.currGen++;   
    this.left = size; 
    this.done = 0; 

    System.out.printf("Starting %d\n", currGen); 

    this.threadPool = Executors.newFixedThreadPool(threads + 4); 

    for (int threadId = 0; threadId < threads; threadId++) { 
     threadPool.submit(new TestWorker(this, currGen)); 
    } 
} 

public synchronized boolean get(long generation) {   
    if (generation != currGen) { 
     return false; 
    } 

    if (this.left == 0) { 
     return false; 
    } 

    this.left--; 

    return true; 
} 

public synchronized void put(long generation) {   
    if (generation != currGen) { 
     return; 
    } 

    this.done++; 

    if (this.done == this.size) { 
     this.run(); 
    } 
} 
} 

Dann wird diese Klasse in der Hauptmethode meines Programms läuft von:

Tester tester = new Tester(30 * 400, 30); 

    tester.run(); 

Beobachtet Verhalten : Die Ausgabe besteht aus Start 1 \ n [...] Start 1034 \ n Danach ist der Prozess noch am Leben, aber es werden keine Zeilen mehr gedruckt. Die Anzahl der Threads für meinen Prozess beträgt 31014 im Moment des Deadlocks. Das Experiment wurde an einer 24-Kern-Maschine durchgeführt.

Erwartetes Verhalten: Der Prozess sollte starten k \ n für k halten Druck = 1, 2, ... für immer oder ein OutOfMemoryError durch zu viele ThreadPools erstellt verursacht werfen.

Das vorgestellte Beispiel verfügt über eine eingeschränkte Fehlersuche. In einem Moment hatte ich mehr printf Befehle, sie impliziert, dass der Deadlock auftritt, wenn alle erstellten Threads der aktuellen Generation ihre publish() Methoden aufgerufen haben, aber die process() Methode wird nicht von der EDT aufgerufen.

+0

Was erwarten Sie? Wenn 'this.done == this.size ', rufen Sie' run() 'auf, was' done' auf null setzt. Eine Nachricht wird nur gedruckt, wenn "this.done> this.size", aber dies ist offensichtlich unmöglich zu erreichen, wenn Sie "done" auf Null setzen, bevor diese Bedingung erreicht wird. Ich bin nicht sicher, ob Sie den Zweck von Thread-Pools verstanden haben, wenn Sie wiederholt neue Pools erstellen ... Aber der Zweck dieses seltsamen Codes ist sowieso unklar. – Holger

+0

Sie drucken die Meldung "Starting ..." direkt vor dem Erstellen eines neuen Thread-Pools, sodass Sie jedes Mal, wenn Sie die Nachricht sehen, einen neuen Thread-Pool mit der festen Anzahl von 15 Threads erstellen. Wenn Sie also die Nachricht "Starting 2813" sehen, haben Sie 2813 × 15 == 42195 Threads erstellt. Und Sie denken, Sie können wirklich eine Beziehung zwischen Ihrem Programm hängen und 42195 Threads erstellt haben, alle von ihnen rufen wiederholt "synchronisierte" Methoden auf dem gleichen Objekt? – Holger

+1

Tut mir leid, wenn das zu hart klingt, aber die Mindestanforderung für eine Frage ist, das erwartete Verhalten zu beschreiben, und es ist unmöglich, die Absicht Ihres Programms zu erraten, da es einfach keinen Sinn ergibt. Alle Threads ändern dieselben Variablen, also ob ein Thread abgeschlossen ist oder nicht, ist rein zufällig. Und da jede Vervollständigung den Start von weiteren 15 Threads auslöst, haben Sie einfach eine Variante der berüchtigten Gabelbombe erstellt. – Holger

Antwort

0

Nach einer Diskussion mit Holger, das Problem durch die Schaffung von Mehrfachthread des verursacht zu sein scheint:

Nachdem das Programm für k Runden ausgeführt hat (d.h. es Starting k\n gedruckt), gab es k ThreadPools die Größe erstellt 34. Von ihnen führen alle Threads außer 30, die die Methode doInBackground() der letzten Generation ausführen, letzten Endes keinen Code aus, sind aber immer noch running. Die 30 Threads, die Code weiterhin ausführen, synchronisieren die Methoden get() und put() und fallen in einen Deadlock, weil der AWT-eventdispatch-Thread aus irgendeinem Grund die Methode publish() nicht ausführt. Dieser Deadlock wird durch die Tatsache verursacht, dass viele Threads erstellt wurden und running (obwohl die meisten von ihnen nicht aktiv sind und nicht am Rennen teilnehmen).

Der Konsens nach einer Diskussion scheint zu sein: Wegen der Erstellung von zu vielen Threads verletzt das System (außerhalb von Java) einige Einschränkungen und es macht JVM nicht mehr weiter.

+0

Sie schauen in die falsche Richtung. Wenn der EDT nicht mehr 'process()' aufruft, kann er weder die Sperre besitzen noch weitere Threads starten. Das würde bedeuten, dass die anderen Threads fortfahren und schließlich in den Wartestatus gehen oder sogar enden sollten, sobald einige der Pools vom Garbage Collector zurückerlangt wurden (es könnte von der Java Version abhängen, ob der Pool eine solche Bereinigung hat). Möglicherweise hängt der EDT beim Versuch, mehr Threads für einen neuen Pool zu erstellen. Das wäre systemabhängig und außerhalb des Anwendungsbereichs des Garbage Collectors. Ich gebe zu, ich habe Ihr Programm nicht mit 24 Kernen ausprobiert ... – Holger

+0

Ich habe den Code im ersten Post geändert, um zu garantieren, dass die in den nachfolgenden Generationen erzeugten Threads nicht rasen. Der neue Code lief für 1034 Generationen und erreichte einen Deadlock. Sehen Sie irgendeine Erklärung, warum ich statt einer Ausnahme einen Deadlock bekomme? – mskrzypczak

+0

Sie erstellen immer noch neue Thread-Pools, auch wenn Sie jetzt eine Referenz darauf in einer Instanzvariablen speichern. Verwenden Sie entweder den gleichen Pool für alle, z. 'if (this.threadPool == null) {this.threadPool = Executors.newFixedThreadPool (threads + 4); } 'oder, wenn du darauf bestehst, einen neuen Pool zu erstellen, triggert das Herunterfahren des alten:' if (this.threadPool! = null) {this.threadPool.shutdown(); } this.threadPool = Executors.newFixedThreadPool (threads + 4); 'Beachten Sie, dass' shutdown() 'die Threads nicht löscht; Sie werden ihre Aufgabe (n) immer noch abschließen, bevor sie enden. Das scheint genau das zu sein, was Sie wollen ... – Holger