Thread Aufgabe - Vorgehensweise

werdas34

Bekanntes Mitglied
Hallo,

ich habe eine Aufgabe bezüglich Thread und frage hier, ob meine geplante Vorgehensweise sinnvoll ist oder nicht. (Threads sind ja nicht ganz so einfach zu handeln.)

Wir haben die Operationen A1, A2, A3, B1, B2, B3, C1, C2 und C3. Sie machen nichts besonderes außer eine gewisse Zeit warten. Diese gilt es zu parallelisieren.

Nun zur Aufgabe:
Löse diese Aufgabe mit dem Java-Idiom „Bedingtes Warten“, das heißt mit wait und notify.
1. Die drei Operationen A1, A2, A3 laufen sequentiell (nacheinander) ab, ebenso die drei Operationen B1, B2, B3 und die drei Operationen C1, C2, C3.
2. C2 startet erst, nachdem alle drei Operationen A1, B1 und C1 beendet sind.
3. C3 startet, sobald zwei der drei Operationen A2, B2 und C2 beendet sind.

Meine geplante Vorgehensweise:
A1, B1, C1 laufen lassen.
A2, B2, C2 in einen Thread packen und mit wait blocken. Das selbe mit A3, B3, C3.
Nachdem A1, B1, C1 durch sind dann den ersten Thread mit notify wecken und dann den zweiten.

Klingt doch sehr sequentiell, aber meine einzige Idee, die Bedingungen zu erfüllen.
Vorallem macht mir die dritte Bedingung Probleme.

Ich hoffe auf Rückmeldung und Verbesserungstipps.

thx
mfg werdas34
 
Ich hätte nur kurz eine Anmerkung: Die Bedingungen 1 und 3 widersprechen sich auch leicht. Bedingung 1 sagt ja, dass jeweils die drei Ketten A1 -> A2 -> A3 und B1 -> B2 -> B3 sowie C1 -> C2 -> C3 sequenziell ablaufen sollen. Also z.B. dürfen nicht A1 und A2 parallel laufen und auch nicht C2 und C3, wohl aber A1 und B1 oder B2 und C2. Wenn wir uns jetzt mal den Abhängigkeitsgraphen zeichnen:
vyfOQmC.png

dann sehen wir, dass durch Bedingung 1 C3 strikt C2 als Vorbedingung hat. Aber Bedingung 3 sagt, dass nur zwei der drei Operationen A2, B2 und C2 Vorbedingung für C3 sein brauchen. Das geht nicht. C2 ist IMMER Vorbedingung für C3, was aus Bedingung 1 hervorgeht, da ja C1 -> C2 -> C3 strikt sequenziell ablaufen müssen. Somit reicht es also für die 3. Bedingung, dass für C3 entweder {A2, C2} oder {B2, C2} als Vorbedingung gegeben sein muss.

EDIT: Also meine Strategie wäre (um die volle erlaubte Parallelität auszunutzen) folgender Ausführungsplan:
wtuP25j.png

Die blauen Pfeile sollen dabei "notifies" sein. Da du bei dem ersten Fence auf zwei Notifizierungen warten musst, musst du hier einen auf ein Lock-Objekt synchronisierten Counter verwenden. Sowas bezeichnet man in Java auch als ein CountDownLatch oder auch CyclicBarrier, welches perfekt für dich hier wäre. Wenn du aber nur Object.wait() und Object.notify() verwenden darfst, dann brauchst du ein Lock-Objekt auf das wait() und notify() ausgeführt wird und einen Counter.
ACHTUNG: Du kannst hier nicht einfach zwei Lock-Objekte verwenden, und sequenziell auf beide warten und von A1 das eine und von B1 das andere Lock-Objekt notifizieren, da du nicht weißt, in welcher Reihenfolge die Notifizierung passieren wird und eine Notifizierung ohne vorangegangenes wait() verpufft.
Für den zweiten Lock reicht ein einfaches wait()/notify(), da du hier ja nur auf einen der beiden A2 oder B2 zu warten brauchst.
 
Zuletzt bearbeitet:
Danke.
Ich habe nun etwas rumprobiert, aber recht funktionieren wills nicht.

Hier erst mal der Code:
Code:
import java.util.concurrent.TimeUnit;

public class OperationsParallel extends Thread {

    static final Object ob = new Object();

    public static void main(String... args) {
        Operation.init(args); //initialisiert die Dauer der Operationen - Default Einstellung ist das jede Operation eine Sekunde braucht.
        ParaA threadA = new ParaA();
        ParaB threadB = new ParaB();
        ParaC threadC = new ParaC();

        long start = System.currentTimeMillis();
      
        threadA.start();
        threadB.start();
        threadC.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException ex) {
        }
        for(int i = 0; i < 9; i++) {
            synchronized (ob) {
                System.out.println("main calls notify");
                ob.notify();
            }
        }
      
        long end = System.currentTimeMillis();
        System.out.println("complete");
        System.out.println(end - start);
    }
}



public class ParaA extends Thread {
    final private Object ob = OperationsParallel.ob;

    public void run() {
        synchronized (ob) {
            try {
                System.out.println(this.getName() + " waits");
                Operation.A1.exec();
                ob.wait();
                Operation.A2.exec();
                ob.wait();
                Operation.A3.exec();
                System.out.println(this.getName() + " ends waiting");
            } catch (InterruptedException ex) {
                System.out.println(this.getName() + " " + ex);
            }
        }
    }
}

ParaB und ParaC sehen genauso aus wie ParaA nur eben mit B1,B2,B3 bzw C1,C2,C3.
Die System.out.println werden nacher auskommentiert, dienen jetzt nur zur Orientierung.

Als Ausgabe erhalte ich:
Thread-0 waits
A1
Thread-2 waits
C1
Thread-1 waits
B1
main calls notify
main calls notify
main calls notify
main calls notify
main calls notify
main calls notify
main calls notify
main calls notify
main calls notify
complete
3013
A2
B2
C2

Das Programm terminiert auch nicht, aber da kommen wir schon zu meinem Problem:
1) Wie stelle ich sicher, das jeder Thread durch notify geweckt wird? Die for-Schleife ist schneller als die Threads.
2)Warum wird complete und die Zeit bereits nach dem ersten Durchlauf der Schleife ausgegeben, jedoch steht sie nicht im Schleifenbody.
3) Was muss ich noch abändern, damit das Programm richtig läuft (mit wait und notify, da dies so in der Aufgabenstellung vermerkt ist?

Als Hinweis habe ich noch diesen Punkt in der Aufgabenstellung:
Ihr Programm verwendet weder sleep, noch Exceptions oder Interrupts. Geben Sie alle InterruptedExceptions weiter.

Ich verwende einmal sleep. Wie umschreibe ich das, dass ich es nicht mehr brauche? Mir fällt dazu nichts ein.
 
Puh... also in dem Code ist so ziemlich alles falsch und den kannst du erstmal wieder komplett wegkicken:
1. Warum rufst du denn in dem "main"-Thread NEUN MAL?! notify() auf einem Objekt auf, auf das sowieso kein Thread synchronisiert?
2. Ja, nächster Punkt: Du rufst notify() auf einem Objekt auf, auf das kein Thread wait() aufruft.
3. Warum rufst du wait() in den jeweiligen Threads zwischen ihren eigenen Operationen auf. Die werden doch sowieso durch die sequenzielle Abarbeitung der Schritte innerhalb desselben Threads nacheinander ausgeführt, brauchen also nicht synchronisiert zu werden, es sei denn, sie haben Operationen anderer Threads als Vorbedingung.
4. Jeder Thread verwendet wieder sein eigenes Objekt, auf das sie sich synchronisieren, das aber kein anderer Thread kennt. Du brauchst ein gemeinsames Lock-Objekt! Außerdem benötigst du einen Counter, um zu zählen, wieviele Operationen schon fertig sind. Das brauchst du z.B., um herauszufinden, ob C2 loslaufen darf.

Ich empfehle dir, nochmal ein paar Artikel zu Threadsynchronisation in Java zu lesen. Z.B. die Javadocs von Object.wait/notify oder https://www.baeldung.com/java-wait-notify
 
1. Warum rufst du denn in dem "main"-Thread NEUN MAL?! notify() auf einem Objekt auf, auf das sowieso kein Thread synchronisiert?
In den ganzen Beispielen wird auch eine while-Schleife verwendet und ich wusste nicht wie ich da elegant ne while Schleife einbau und obs funktioniert, deswegen einfach mal ne grobe for-Schleife.

2. Ja, nächster Punkt: Du rufst notify() auf einem Objekt auf, auf das kein Thread wait() aufruft.
Ich rufe in ParaA ob.wait() auf. Dazu ist ob vom Typ Object den alle drei Threadklassen gemeinsam haben. Entweder verstehe ich dich falsch oder keine Ahnung.

3. Warum rufst du wait() in den jeweiligen Threads zwischen ihren eigenen Operationen auf. Die werden doch sowieso durch die sequenzielle Abarbeitung der Schritte innerhalb desselben Threads nacheinander ausgeführt, brauchen also nicht synchronisiert zu werden, es sei denn, sie haben Operationen anderer Threads als Vorbedingung.
Wenn ich diese Threads normal (ohne mittendrin mit wait()) durchlaufe dann startet Thread A mit der Ausgabe A1 A2 A3. Jedoch soll A1 B1 C1 anfangen. Zumindest war so meine Denkweise.
Jetzt bei erneutem Durchlesen der Bedingungen sehe ich das A1 A2 A3 eine korrekte Vorgehensweise ist.
Das einzoge worauf ich achten muss ist, dass C2 erst nach A1 B1 C1 startet. Heißt für mich. Ich lasse A1 A2 A3 laufen dann B1 B2 B3 dann sind alle Bedingungen erfüllt und C1 C2 C3 kann laufen. Aber das ist doch dann sequentiell. Was genau soll ich da dann parallelisieren?

4. Jeder Thread verwendet wieder sein eigenes Objekt, auf das sie sich synchronisieren, das aber kein anderer Thread kennt. Du brauchst ein gemeinsames Lock-Objekt! Außerdem benötigst du einen Counter, um zu zählen, wieviele Operationen schon fertig sind. Das brauchst du z.B., um herauszufinden, ob C2 loslaufen darf.
Es gibt ein final static Object ob den alle Para(A,B,C) sich teilen.
Und mit dem Counter, wie soll ich das anstellen.
Code:
 i = 0;
Operation.A1.exec();
i = i + 1 ;
Operation.A2.exec();
i = i + 1 ;
Operation.A3.exec();
i = i + 1 ;

Ich empfehle dir, nochmal ein paar Artikel zu Threadsynchronisation in Java zu lesen. Z.B. die Javadocs von Object.wait/notify oder https://www.baeldung.com/java-wait-notify
Habe ich gemacht. Auch ein paar Videos angesehen. Ich verstehe das auch.
Entweder sind die Beispiele verständlicher(send and receive) oder ich raff die Aufgabe nicht.

Kannst du nicht vielleicht ein grobes Beispiel machen anhand dem ich mich orientieren kann?

Edit:
Muss ich eigentlich für A1 A2 A3 einen eigenen Thread schreiben oder reicht es wenn ich ihn in der main durchlaufen lasse? Dafür die anderen beiden B1 B2 B3 und C1 C2 C3 irgendwie zu parallelisieren?
 
Zuletzt bearbeitet:
In den ganzen Beispielen wird auch eine while-Schleife verwendet[...]
Was denn für ganze Beispiele? Ich kann dir auch Beispiele nennen, wo for-Schleifen verwendet werden. Aber, ob das Beispiel dann auch das tut, was wir wollen, ist dann eine andere Frage...
Ich rufe in ParaA ob.wait() auf. Dazu ist ob vom Typ Object den alle drei Threadklassen gemeinsam haben. Entweder verstehe ich dich falsch oder keine Ahnung.
Nur, weil der Typ der Variablen überall java.lang.Object ist, heißt das doch nicht, dass alle Threads deswegen auf dieselbe Variable zugreifen... Bitte lerne ganz dringend ersteinmal Java-Grundlagen: Was sind Variablen, was sind Objekte, was sind Klassen, was sind Felder/Attribute einer Klasse, etc.
Das einzoge worauf ich achten muss ist, dass C2 erst nach A1 B1 C1 startet.
Korrekt.
Heißt für mich. Ich lasse A1 A2 A3 laufen dann B1 B2 B3 dann sind alle Bedingungen erfüllt und C1 C2 C3 kann laufen. Aber das ist doch dann sequentiell. Was genau soll ich da dann parallelisieren?
Nein. A1, B1 und C1 können parallel zueinander laufen. Ebenso A2, B2 und C2 und genauso auch A3, B3 und C3.
Es gibt ein final static Object ob den alle Para(A,B,C) sich teilen.
Nein! Aktuell definiert sich jede ParaA/B/C Klasse sein eigenes private Feld...
Muss ich eigentlich für A1 A2 A3 einen eigenen Thread schreiben oder reicht es wenn ich ihn in der main durchlaufen lasse? Dafür die anderen beiden B1 B2 B3 und C1 C2 C3 irgendwie zu parallelisieren?
Es reicht, wenn du nur zwei zusätzliche Threads startest. Parallel laufen müssen drei Threads. Der "main" Thread kann natürlich einer der dreien sein.
 
Mir war gerade langweilig. Falls du einfach nur eine Lösung kopieren willst, hier ist eine:
Java:
import java.util.function.Supplier;
public class ParallelTask {
  private static boolean A1_done, B1_done, A2_done, B2_done;
  private synchronized static void markDone(Runnable r) {
    r.run();
    ParallelTask.class.notify();
  }
  private synchronized static void waitFor(Supplier<Boolean> predicate) {
    while (!predicate.get()) {
      try {
        ParallelTask.class.wait();
      } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
      }
    }
  }
  private static void a() {
    System.out.println("A1");
    markDone(() -> A1_done = true);
    System.out.println("A2");
    markDone(() -> A2_done = true);
    System.out.println("A3");
  }
  private static void b() {
    System.out.println("B1");
    markDone(() -> B1_done = true);
    System.out.println("B2");
    markDone(() -> B2_done = true);
    System.out.println("B3");
  }
  private static void c() {
    System.out.println("C1");
    waitFor(() -> A1_done && B1_done);
    System.out.println("C2");
    waitFor(() -> A2_done || B2_done);
    System.out.println("C3");
  }
  public static void main(String[] args) {
    /* Starte zwei parallele Threads zum Abarbeiten der B und C Tasks. */
    new Thread(ParallelTask::c).start();
    new Thread(ParallelTask::b).start();
    /* Arbeite die A Tasks im main-thread ab. */
    a();
  }
}
 
Zuletzt bearbeitet:
Nur, weil der Typ der Variablen überall java.lang.Object ist, heißt das doch nicht, dass alle Threads deswegen auf dieselbe Variable zugreifen... Bitte lerne ganz dringend ersteinmal Java-Grundlagen: Was sind Variablen, was sind Objekte, was sind Klassen, was sind Felder/Attribute einer Klasse, etc.
Ich erzeuge in der Main ein static final Object ob;
Wenn ich ParaA instanziere dann ensteht das Objekt threadA vom Typ ParaA. Nun hat threadA ein Attribut ob und diesem Attribut ob weise ich das in der Main erzeugte ob zu. (Also die Referenz natürlich)
Heißt: Ich erzeuge ein Objekt in der Main und weise sie dann drei neuen Objekten zu. diese Objekte haben dann das eine in der Main erzeugte Objekt inne, was final ist, sich also nicht ändern kann.

Kannst du mir bitte erklären was das Programm stattdessen macht (nur auf den Punkt mit dem Monitor Objekt bezogen)? Oben ist meine Verstehensweise.


Mir war gerade langweilig. Falls du einfach nur eine Lösung kopieren willst, hier ist eine:
Danke. Aber ich brauche keine vorgefertigte Lösung. Ein anderers Beispiel bzw der Anfang hätte schon gereicht, damit es klarer wird was zu tun ist.
 

Neue Themen


Zurück
Oben