Endlosschleife Thread sauber beenden

BodyLAB

Bekanntes Mitglied
Guten Tag zusammen,

ich hab eine Aufgabe bekommen, bin absoluter Anfänger in der Thread-Welt (Parallelität) und wüsste gerne wie man sauber einen Thread beenden lassen kann.

Mein vorgehen war dieses hier:
erst einmal die Schleife
Java:
while (true) {
                char quit = (char) br.read();
                if (quit == 'q')
                    for (Barrier b : arr)
                        b.stopRunning();
                System.exit(0);
            }
################
Hier die Barrier Klasse:
Java:
import java.util.concurrent.atomic.AtomicBoolean;

public class Barrier extends Thread {
    private String name;
    private int begin, end, open, close, count;
    private AtomicBoolean running = new AtomicBoolean(true);

    Barrier(String name, int begin, int end, int open, int close, int count) {
        this.name = name;
        this.begin = begin;
        this.end = end;
        this.open = open;
        this.close = close;
        this.count = count;
    }
    
    public String getBarrierName() {
        return name;
    }

    public int getBegin() {
        return begin;
    }

    public int getEnd() {
        return end;
    }

    public int getOpen() {
        return open;
    }
    
    public int getClose() {
        return close;
    }
    
    public AtomicBoolean getRunning() {
        return running;
    }
    
    public void setBarrierName(String name) {
        this.name = name;
    }
    
    public void setBegin(int begin) {
        this.begin = begin;
    }
    
    public void setEnd(int end) {
        this.end = end;
    }
    
    public void setOpen(int open) {
        this.open = open;
    }

    public void setClose(int close) {
        this.close = close;
    }
    
    public void setRunning(AtomicBoolean running) {
        this.running = running;
    }
    
    public void stopRunning() {
        running.set(false);
    }
    
    public void run() {
        for (; count != 0 && running.get(); count--) {
            try {
                System.out.println("Barrier " + name + " start opening");
                sleep(open);
                System.out.println("Barrier " + name + " end opening");
                sleep(end);
                System.out.println("Barrier " + name + " start closing");
                sleep(close);
                System.out.println("Barrier " + name + " end closing");
                sleep(begin);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}



Nun findet man im Netz einiges darüber doch ich weiß nicht ob wir einen AtomicBoolean nutzen dürfen. Der erste Versuch war mit einem normalen Boolean in der Barrier Klasse der dann auf false geschaltet wurde. Doch dort wurde als Begründung angegeben das die Threads dann nicht vernünftig beendet worden wären. Nun habe ich von einem Kommilitonen erfahren das er das von außerhalb macht. Des weiteren meinte er das die while(true) Schleife dann zu einer while(variable) Schleife geändert werden soll und die variable "einfach" auf false gesetzt werden kann.
MyClass.variable = false (Damit lässt sich der Thread von außerhalb anhalten) Wie genau sieht so etwas aus.

Hat jemand eine Idee oder sind meine Ansätze zu weit her geholt oder zu abstrakt? Den Falschen Code kann ich auch gerne bei bedarf noch Posten 🙂
 
Ds Grundprinzip besteht darin, dem Thread zu signalisieren, dass er seine Arbeit einstellen kann. Bei Dir wäre das ein Aufruf von Barrier#stopRunning. Es empfiehlt sich, ein wenig auf die Beendigung zu warten und ggf. eine Unterbrechungsanforderung hinterherzuschicken (s. Thread#join und #isAlive und #interrupt).
 
Die meisten Endlosthreads - wie deiner - laufen nicht ständig auf 100% Last, sondern haben oft auch ein Sleep mit drin.
Deshalb empfiehlt es sich, in stopRunning() Methode auch ein notify() mit einzubauen.
Was mir bei deinem Code nicht ganz so gefällt, ist, dass da ohne Vorbedingung mehrere Sleeps mit drin sind.
Ich würde nach jedem Kommando, das auf ein Sleep folgt prüfen, ob der Thread überhaupt noch laufen soll. Falls die Schleife aber auf jeden Fall auch nach dem Abbruchkommando noch fertig abgearbeitet werden soll, erübrigt sich das natürlich.

Das AtomicBoolean ist sicher kein Fehler, aber etwas Overkill. Ein normaler Boolean reicht hierfür.
setRunning(boolean) ist überflüssig, es reicht, die Variable am Anfang von run() auf true zu setzen. Die methode isRunning() sagt natürlich auch nur etwas über den Zustand der Variable aus, nicht über den Thread. Hierfür gibt's isAlive().
 
Nein, der boolean muss zumindest volatile deklariert werden.
Nuja, jedenfalls funktionieren meine Threads auch mit einer einfachen privaten boolean-Variable, auch wenn sie's anscheinend nicht sollten.
"stopWhateverYourDoing()" setzt sie auf false, in der Run-Methode wird sie abgefragt. Allerdings hab ich auch keine Timings, wo's auf Millisekunden ankommt.
 
Das bedeutet wenn ich den Thread von außerhalb beenden möchte müsste ich eine Methode stopRunning in Barriere Definieren und diese dann in der While Schleife ausführen. Wäre das korrekt?
Leider hatten wir bis jetzt noch kein volatilen und notify etc =( Deswegen glaube ich auch das wir AtomarBoolean nicht verwenden dürfen.

Wenn ich die Schleife einfach bei drücken von q beende, werden dann auch auf einen schlag sämtliche Threads beendet oder laufen die noch zu ende? Ich würde sagen Theoretisch müssen diese noch zu ende laufen da ja keine Synchronisationsmechanismen eingebaut sind oder?
 
Deine Vermutung geht in die richtige Richtung. Deine Threads werden durch die Variable nicht direkt beeinflusst und laufen auf jeden Fall so lange weiter, bis sie durch das Prüfen der Variable als Abbruchbedingung beendet werden.
Allerdings beendet System.exit(0) alle Threads sofort.
Falls Timings relevant sind, stellst du mit Volatile sicher, dass auch wirklich der echte Variableninhalt und kein früherer gecachter abgefragt wird, im Normalfall ist das aber unwichtig (es ist aber auch kein Schaden, wenn man's doch macht).
Um einen Thread mit Gewalt zu beenden, gibt's die stop() Methode, die ist aber deprecated und es wird dringend davon abgeraten. Lieber eine saubere Abbruchbedingung einbauen, wie ihr das anscheinend gerade lernt.
 
Allerdings beendet System.exit(0) alle Threads sofort.
Das Problem scheint genau das gewesen zu sein. Denn System.exit() wird als "unsauberes Beenden" gewertet. Ein "sauberes Beenden" ist per Definition immer dann, wenn die Main-Methode (wie auch alle anderen Threads) ihr reguläres Ende erreicht.

Es scheint wohl so das sie nur auf den normalen boolean raus wollten. Wie gesagt volatile hatten wir noch nicht, deswegen mach ich Ihn nur private.


Es empfiehlt sich, ein wenig auf die Beendigung zu warten und ggf. eine Unterbrechungsanforderung hinterherzuschicken (s. Thread#join und #isAlive und #interrupt).
Das hab ich leider nicht so ganz verstanden. Das liegt sicherlich daran das der Stoff noch fehlt. isAlive ist eine Methode von Thread die mir sagt ob der Thread noch lebt, wie könnte man das hier nutzen wenn ich noch weiter Fragen stellen darf.

(Bearbeitet: Eine Frage kam mir gerade noch. Ich hab die while(true) Schleife auch durch einen boolean go = true; while(go) geändert. Ist es möglich die Abfrage zu änderen dahin gehend das wenn man beendet der Thread die Schleife beendet. Also so etwas wie Barrier.isAlive() <- dieser Thread "lebt" nun nicht mehr also wird die Schleife beendet und somit auch die Main etc 🙂 )

Denn an sich ist das Thema an dieser Stelle ja ausreichend behandelt und beantwortet worden ;-)

Danke für die nette Hilfe
 
Zuletzt bearbeitet:
Thread.join() ist z.B. sinnvoll, wenn jeder Durchlauf des Threads eine bestimmte Zeit benötigt, nach dem Aufruf von stopRunning() beendet er ja nicht sofort. Damit kannst du warten, bis er auch ganz sicher beendet wurde. Du kannst in diesem Beispiel zuerst das Beenden aller Threads aufrufen und danach auf alle Threads warten, bevor du mit System.exit das Programm beendest.
 
Meinst du das mit join vielleicht so?
Java:
while (go) {
                char quit = (char) br.read();
                if (quit == 'q') {
                    for (Barrier b : arry) {
                        b.stopIsRunning();
                        b.join();
                    }
                    go = false;
                }

Wenn ich das jetzt aber richtig verstehe würde das doch sicherlich jede Menge overhead Produzieren oder? So wartet nun jeder Thread auf das beenden des vorherigen oder ist das in Ordnung so?

Bin gerade auch etwas verwirrt! Glaube da muss ich mir mal Lektüre dazu nehmen so wird das nichts 😡
 
Nein, die Threads sollen weiterhin parallel beendet werden.

[CODE lang="java" title="Hintereinander"] //Zuerst allen das Kommando zum Beenden schicken
for (Barrier b : arry) {
b.stopRunning();
}
//Dann sicherstellen, dass alle beendet sind, bevor's weitergeht
//Die Reihenfolge, in der sie beendet werden, ist dabei nicht wichtig.
//Wenn z.B. der erste in der Liste zuletzt beendet, dann geht's bei den restlichen joins sofort weiter.
for (Barrier b : arry) {
b.join();
}
[/CODE]

Über Overhead brauchst du dir bei einem UI keine Gedanken machen, da ist wichtiger, dass alles sauber läuft und nachvollziehbar ist. Sobald du mal anfängst, Grafikkartentreiber zu schreiben, schaut's natürlich anders aus.
 
Okay jetzt habe ich das glaube ich auch besser verstanden.
Dann warte ich nun auch mal ab wie es weiter geht ;-)

Werde hier aktiv dabei bleiben. Ich lese auch inzwischen oft in den anderen Threads mit weil vieles erneut beschrieben wird was man verdrängt hat oder man vielleicht auch mal helfen kann 🙂
 
Sorry, bei mir war seit Mittag das Netz abgekackt, aber @Neumi5694 hat ja eh schon alles beantwortet 🙂 Eins noch: join kann man auch ein Timeout mitgeben, damit man nicht ewig auf die Beendigung des Threads wartet.
 
Thread.join() ist z.B. sinnvoll, wenn jeder Durchlauf des Threads eine bestimmte Zeit benötigt, nach dem Aufruf von stopRunning() beendet er ja nicht sofort.
Das stimmt: join wird verwendet, wenn der Thread seine Aufgabe zu Ende bringen soll. Und man auf ihn warten kann und muss.
Im Falle des obigen Beispiels ist es die Aufgabe des Threads nur in gewissen Intervallen zu schlafen. In solchen Fällen greift man besser auf interrupt() zurück. --> Thread wird sofort beendet.
 
Falls Timings relevant sind, stellst du mit Volatile sicher, dass auch wirklich der echte Variableninhalt und kein früherer gecachter abgefragt wird, im Normalfall ist das aber unwichtig (es ist aber auch kein Schaden, wenn man's doch macht).
Im Normalfall ist es wichtig, dass die Variable volatile ist – den Schaden in Form einer wirklichen Endlosschleife hat man eher wenn man es nicht nutzt. Nur mit volatile kann in dem Fall man erreichen, dass immer der aktuelle Zustand genutzt wird, ohne kann uU dauerhaft der alte Zustand genutzt werden, ohne ist es eher ein Glücksfall, wenn der aktuell Zustand wirklich gelesen wird (im wesentlichen dann, wenn die JVM nicht zum optimieren kommt).

In solchen Fällen greift man besser auf interrupt() zurück. --> Thread wird sofort beendet.
interrupt beendet keine Threads, es führt nur in blockierenden Aufrufen zu einer InterruptedException, welche den Thread dann implizit beenden würde, wenn man sie nicht fängt. Wenn es aber keine blockierenden Aufrufe gibt, muss man interrupted selbst abfragen und den Thread selbst beenden.
 
interrupt beendet keine Threads, es führt nur in blockierenden Aufrufen zu einer InterruptedException, welche den Thread dann implizit beenden würde, wenn man sie nicht fängt.
Das das über die Exception läuft ist klar. Mein Post bezieht sich auf join. Und sleep. Was zu langen Wartezeiten führt, die gegebenenfalls nicht erwünscht sind. Es empfiehlt sich sogar eine Flag einzuführen, um zwischen einem gewollten und einem nicht gewollten interrupt zu unterscheiden.
 
Das das über die Exception läuft ist klar. Mein Post bezieht sich auf join. Und sleep. Was zu langen Wartezeiten führt, die gegebenenfalls nicht erwünscht sind. Es empfiehlt sich sogar eine Flag einzuführen, um zwischen einem gewollten und einem nicht gewollten interrupt zu unterscheiden.
In dem Code oben wird die Exception gefangen, das interrupt würde in dem Fall nichts bewirken außer den Stacktrace ausgeben, aber wollte ich darauf aufmerksam machen, das es nicht "einfach so" irgendwas beendet 🙂

Wenn es für alle klar wäre, was interrupt macht, würde man solchen Code wie oben nicht sehen 😉
 
Das liegt sicherlich daran das der Stoff noch fehlt. isAlive ist eine Methode von Thread die mir sagt ob der Thread noch lebt, wie könnte man das hier nutzen wenn ich noch weiter Fragen stellen darf.

Skizze:
Java:
    final int TIMEOUT = 250;
    for (Barrier b : arry) {
        b.stopRunning(); // "Beendigungsanfrage" stellen
    }

    // Thread.sleep(TIMEOUT);

    for (Barrier b : arry) {
        b.join(TIMEOUT);
        if (b.isAlive()) {  
            b.interrupt();
        }
    }

In Zeile 9 wird auf die Beendigung eines jeden Threads gewartet. Wenn viele Threads nicht wollen, summiert sich das natürlich. Man kann stattdessen auch vor der Schleife allen Threads ein wenig Zeit zur Beendigung geben (Zeile 6).

Mit b.isAlive() schauen wir nach, ob der Thread immer noch läuft. Dann kann man davon ausgehen, dass der Thread blockiert und schickt ein interrupt hinterher. Man könnte jetzt den Threads nochmals Zeit geben und im Anschluss prüfen, ob sie alle beendet sind. Falls nicht kann man eine Warnmeldung ausgeben, ändern lässt sich aber zur Laufzeit daran nichts mehr. Hier mal eine Skizze ohne join:
Java:
    final int TIMEOUT = 250;
    for (Barrier b : arry) {
        b.stopRunning(); // "Beendigungsanfrage" stellen
    }

    Thread.sleep(TIMEOUT); // allen Threds etwas Zeit geben

    for (Barrier b : arry) {
        if (b.isAlive()) {  // Thread lebt immer noch?
            b.interrupt();  // dann Holzhammer
        }
    }

    Thread.sleep(TIMEOUT); // allen Threads wieder etwas Zeit geben

    for (Barrier b : arry) {
        if (b.isAlive()) { // Thread lebt immer noch? Das Ding ist unkaputtbar, nicht schön, aber so isses halt.
            System.out.printf("WARNUNG: Thread %s konnte nicht beendet werden.\n", b.getName());
        }
    }

Natürlich kann man auch einfach von vornherein gleich ein interrupt() mitgeben. Als Softi ist mir das aber zu brutal 🙂
 
Wow nochmals danke für eure Beiträge.
Sehe schon hier sind viele Profis unterwegs ;-) da freut man sich ungemein über Produktive Kommentare 🙂

Wenn ich die Zeilen code hier sehe verstehe ich auch so langsam wie das funktioniert wenn mehrere Threads ablaufen, denn man weiß ja nie wo ein Thread im Programmcode sich befindet.
Das ist nämlich ein sehr großes Problem von mir gerade jetzt am Anfang, umzudenken von Sequenziellen Programmen die alleine ablaufen zu Programmen wo jetzt mehrere gleichzeitig arbeiten.

Da hilft nur Üben, Fehler machen und Hilfe nehmen um dann bei Fehlern ab einem gewissen Faktor Investierte Zeit hier nach zu haken.

Die Thread.interrupt() Methode muss ich mir dann auch mal noch anschauen, doch immer eins nach dem anderen ;-)

Vielleicht mal noch kurz kennt jemand eine Seite wo Übungsaufgaben für Parallele Programmierung gepostet werden, vielleicht sogar mit Lösungsansätzen?
 

Zurück
Oben