In einem Thread Endlosschleife beenden

BodyLAB

Bekanntes Mitglied
Hallo zusammen,

ich hab eine Frage zu meiner Klasse. Es gibt insgesamt vier Klassen, Car, Queue, Garage und
Java:
public class CarMechanic extends Thread {

    private Queue q;
    private int id;

    CarMechanic(Queue q, int id) {
        this.q = q;
        this.id = id;
    }

    public void run() {
        while (true) {
            Car c = q.getCar();
            if (c != null) {
                c.repair(id);
            } else {
                try {
                    sleep(1000);
                } catch (Exception ex) {
                    System.out.println(ex.getMessage());
                }

            }
        }
    }
}

Meine Frage ist, wie bekomme ich in der run Methode die Endlosschleife weg? Meine Vorstellung wäre, ich möchte die size der Queue abfragen und sobald sich der Zustand der Queue ändert, sollen meine Mechaniker arbeiten. Sobald die Queue dann leer ist, soll das Programm beendet werden.

Für ein solche vorhaben müsste ich ein Observer nutzen oder geht das irgendwie auch noch anders?

In der Garage erstelle ich die Mechaniker, dort gibt es eine Liste mit Autos (Cars) und die Mechaniker nehmen sich Autos aus dieser Liste. Wie oben beschrieben wäre es toll wenn ich diese Endlosschleife beenden könnte. Mir fällt dazu aber nur Observer ein. Ist das vielleicht zu "Overkill" geht es auch "einfacher"? Würde mich sehr über eine Antwort von euch freuen 🙂

Hier auch noch sehr gerne die restlichen Klassen:
Garage:
Java:
public class Garage {

    private Queue q = new Queue();

    public Garage() {
        for (int i = 0; i < 10; i++)
            new CarMechanic(q, i).start();
    }

    public void dropCar(Car car) {
        q.putCar(car);
    }
    
    public static void main(String[] args) {
        Garage g = new Garage();
        for(int i = 0; i < 120; i++)
            g.dropCar(new Car());
        
    }

}

Queue:
Java:
import java.util.ArrayList;

public class Queue {
    private ArrayList<Car> element;

    Queue() {
        element = new ArrayList<>();
    }

    public synchronized void putCar(Car c) {
        element.add(c);
    }

    public synchronized Car getCar() {
        if (element.isEmpty())
            return null;
        Car c = element.get(0);
        element.remove(0);
        return c;
    }

    public boolean isEmpty() {
        return element.isEmpty();
    }

}
(Die Methode putCar müsste nicht Synchronized sein, weiß ich, hab es aber jetzt so gelassen 😀

Car:
Java:
public class Car {
    private int id;
    static int i = 0;

    Car() {
        id = i++;
    }

    public void repair(int id) {
        System.out.println("Mechaniker " + id + ": Auto " + this.id + " wird repariert");
        try {
            Thread.sleep((int) (Math.random() * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public int getId() {
        return id;
    }
}
 
Nur als Hinweis außerhalb des Themas.
Dieser Code:
Java:
        public synchronized Car getCar() {
            if (element.isEmpty())
                return null;
            Car c = element.get(0);
            element.remove(0);
            return c;
        }
ist äquivalent zu:
Java:
        public synchronized Car getCar() {
            if (this.element.isEmpty()) {
                return null;
            }
            return this.element.remove(0);
        }
 
Du hast doch Queue.isEmpty() implementiert. Das wäre doch die Abbruchbedingung die du suchst oder verstehe ich dich gerade falsch?
Das Problem ist, das die Threads (Mechaniker) vorher los laufen und noch keine Daten in der Queue vorhanden sind.

Eine BlockingQueue waere eine Moeglichkeit. Diese blockiert den Thread dann solange bis es etwas zum entnehmen gibt.
Klaro könnte man andere Klassen nutzen, dass sollen wir aber nicht ;-)
 
Im Garage Konstruktor erzeuge ich doch die Threads. Doch wenn die Threads in der run Methode die Variable überprüfen würden, dann wäre dort doch noch nichts! Das ganze kommt doch erst später oder sehe ich das Falsch?

Java:
Garage g = new Garage();  //hier erzeugen wir die Threads, diese laufen schon mal los
        for(int i = 0; i < 120; i++)
            g.dropCar(new Car()); //hier kommt das erste Auto in die Queue, somit ist die Queue oben noch leer!
 
Im Garage Konstruktor erzeuge ich doch die Threads. Doch wenn die Threads in der run Methode die Variable überprüfen würden, dann wäre dort doch noch nichts! Das ganze kommt doch erst später oder sehe ich das Falsch?
Ich verstehe das Problem nicht. Ein Mechaniker kommt morgens zur Arbeit in die Garage. Sollte kein Car in der Garage sein, welches repariert werden muss, dann wartet er bis es Arbeit gibt. Kommt dann ein Kunde wird er sofort bedient. Kommen mehrere Kunden in kurzer Zeit hintereinander werden sie sequentiell bedient. Ist kein Car zur Reparatur vorhanden, dann muss der Mechaniker von zeit zu Zeit nachsehen ob evtl. Arbeit vorhanden ist.
Man könnte das auch über wait() und notify() lösen, statt mit Thread.sleep aber das Problem bleibt. Der Thread muss laufen um Arbeit zu verrichten oder in Bereitschaft zu sein. Für eine Lösung mit wait() und notify() müsste aber wahrscheinlich einiges umgebaut werden. ich habe so etwas auch schon ewig nicht mehr gemacht. Das muss man heute praktisch nicht mehr, da sich Java auch immer weiter entwickelt, s. BlockingQueue.
 
Es geht ihm mit der Aussage wahrscheinlich um die Abbruchbedingung Queue#isEmpty anstelle der Endlosschleife. Zumindest hab ich das so verstanden. Die kann er halt nicht nehmen weil zu beginn des Programms noch keine Elemente in der Queue liegen.

Ansonsten: ja, der Thread muss blockieren bis es etwas zu tun gibt (Queue ist nicht mehr leer). Das kann auf verschiedene Weise gemacht werden. Wait/Notify hab ich persönlich noch nie so wirklich mit gearbeitet. Als einfache Lösung käme mir hier ein Polling mittels Thread#sleep in den Sinn.

Da sähe die run Methode in etwa so aus:
Code:
@Override
public void run()
{
	while(q.isEmpty())
	{
		try
		{
			Thread.sleep(1000);
		}
		catch(InterruptedException e)
		{
			e.printStackTrace();
		}
	}
	
	while(!q.isEmpty())
	{
		Car c = q.getCar();
		if (c != null)
		{
			c.repair(id);
		}
	}
	
	System.out.println(getName() + " quits working");
}

Nebenbei. Man kann Threads mit setName einen Namen geben. Dann sieht das auch im Debugger sehr nett aus. Es steht dann nicht immer nur "Thread [Thread-0], Thread [Thread-1]" etc.
 
@mihe7, @Oneixee5 und @X5-599 Danke für die Antworten 🙂

Ich hab das ganze wohl verquert getextet. JA es ging mir um die Abbruchbedingung in der run Methode des Mechanikers.
Mit Sleep könnte man das machen stimmt, aktiv warten bis etwas in der Queue ist. Danach würde das Programm die Autos einfügen und die Mechaniker Threads arbeiten die Autos ab und sind am Abend fertig und das Programm beendet 😃

An wait() und notify() dachte ich gestern Abend auch noch. Vielleicht könnte man die Threads ja erst mal alle warten lassen -> wäre zwar blöd, doch ich wollte diese Endlosschleife da raus bekommen 🤣 Das Probier ich später mal vielleicht klappt es ja. Anders nehme ich die Schläfrigen Threads 😇

___
Ich dachte an ein Observer, weil der hätte dann ja den Thread sagen können, so jetzt ist etwas in der Queue drin und ihr könnt los legen (falls so etwas geht)
 
JA es ging mir um die Abbruchbedingung in der run Methode des Mechanikers.
Genau deswegen habe ich auch gefragt, ob die leere Queue ein gutes Kriterium ist.

Das wäre sicher der Fall, wenn im Vorfeld bekannt ist, was der Mechaniker den Tag über zu tun hat. Der Inhalt der Queue steht dann bereits fest, bevor die Mechaniker die Arbeit beginnen und es gibt überhaupt kein Problem.

Wenn dagegen neue Aufträge reinkommen können, während der Mechaniker arbeitet, stellt sich natürlich die Frage: woran erkennt der Mechaniker denn, wann Feierabend ist?

Mal im real life: Vormittag kommt kein Kunde, die Arbeiten an den auf dem Hof stehenden Autos sind abgeschlossen. Hat der Mechaniker nun Feierabend? Es könnte ja auch um 14:00 Uhr der nächste Kunde kommen.

Der Mechaniker hat Feierabend, wenn Cheffe sagt, dass er nach Hause gehen kann. So etwas lässt sich z. B. mit Poison Pills simulieren: jeder Mechaniker nimmt so lange Aufträge aus der Queue, bis er einen "Feierabendauftrag" erhält.

Vielleicht könnte man die Threads ja erst mal alle warten lassen -> wäre zwar blöd, doch ich wollte diese Endlosschleife da raus bekommen 🤣
Nein, das ist nicht blöd. Thread wird schlafen gelegt, sobald etwas in der Queue liegt, wird aufgeweckt. Zusammen mit den "Feierabendaufträgen" hast Du dann eine runde Sache.
 
Das höre ich zum ersten mal. Muss ich mal schauen ob ich was dazu finde.

Nein, das ist nicht blöd. Thread wird schlafen gelegt, sobald etwas in der Queue liegt, wird aufgeweckt. Zusammen mit den "Feierabendaufträgen" hast Du dann eine runde Sache.
Darum ging es mir an sich auch. Nur kenne ich mich mit Threads noch nicht so gut aus und weiß nicht wie die sich verhalten und wie man sie "gut" steuern kann etc. 😵

Doch wir sind ja fleißig da wird es von mal zu mal besser 😉

___
Okay, das mit den Feierabendauftrag finde ich jetzt noch verzwickt. Muss ich dann jedem Mechaniker eine ID geben und den Aufträgen in meiner Queue gebe ich dann auch eine ID, also den Feierabendauftrag. Sobald die beiden IDs dann gleich sind kann der Mechaniker seine Arbeit beenden. Wie kill ich dann diesen Thread? 🤔 Das mit den IDs gefällt mir irgendwie auch nicht so ganz. 🤔
 
Zuletzt bearbeitet:
Muss ich dann jedem Mechaniker eine ID geben
Nein, es geht nur darum, dass Du ein Objekt hast, das sich von echten Aufträge unterscheidet. Deine Aufträge sind ja Cars. Wenn wir mal dabei bleiben, könntest Du einfach einen NONE-Car definieren:
Java:
public class Car {
    private int id;
    static int i = 0;

    public static final Car NONE = new Car(-1); // EDIT: korrigiert

    private Car(int id) {
        this.id = id;
    }

    Car() {
        id = i++;
    }

    public void repair(int id) {
        System.out.println("Mechaniker " + id + ": Auto " + this.id + " wird repariert");
        try {
            Thread.sleep((int) (Math.random() * 1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public int getId() {
        return id;
    }
}

Normalerweise würdest Du noch equals und hashCode überschreiben, dann könntest Du einen Car direkt mit Car.NONE vergleichen, ohne geht es halt nur über die IDs.

Dein Garage-Objekt weiß, dass es n Mechaniker gibt. Also kannst Du dort eine Methode zum Abschließen der Aufträge einbauen, die nichts anderes macht, als n-mal Car.NONE in die Queue einzufügen.

Diese Methode wird aufgerufen, nachdem alle echten Aufträge eingefügt wurden:
Java:
    public static void main(String[] args) {
        Garage g = new Garage();
        for(int i = 0; i < 120; i++)
            g.dropCar(new Car());
        g.finish(); // schickt die Mechaniker in den Feierabend.
    }

Die Schleife beim Mechaniker läuft nun einfach genau so lange, bis man auf ein Car.NONE trifft. Die run-Methode wird dadurch verlassen, womit auch der Thread beendet wird.

Beispielhafte Skizze (hier wäre getCar() ein blockierender Aufruf):
Java:
Car car = q.getCar();
while (car.getId() != Car.NONE.getId()) {
    processCar(car);
    car = q.getCar();
}

Hier den Car zu missbrauchen, ist ein klein wenig quick & dirty; aber es geht an der Stelle nur mal ums Prinzip.
 
Zuletzt bearbeitet:
Bei mir bleibt das Problem gleich!

Die Threads laufen los, schauen ob etwas in der Queue ist und sind fertig oder man bekommt null oder oder oder ....

Sobald das erste Auto in der Queue liegt müsste ich das erkennen können.
 
Die Threads laufen los, schauen ob etwas in der Queue ist und sind fertig oder man bekommt null oder oder oder ....

Sobald das erste Auto in der Queue liegt müsste ich das erkennen können.
Beim Aufruf von getCar() muss der Thread einfach warten, bis es etwas aus der Queue zu entnehmen gibt. Das lässt sich recht einfach mit Object#wait/notify (oder notifyAll) erledigen.
 

Zurück
Oben