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.
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
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
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