[Java] Multi-Threading um brauchbares Ergebnis schneller zu finden

cppnap

Lt. Junior Grade
Registriert
Nov. 2008
Beiträge
487
Guten Tag,

ich befasse mich gerade zum ersten Mal etwas mehr mit Concurrency. Ich habe eine mathematische Konstruktion die ich über Multi-Threading lösen möchte.
Dabei sollen eine fixe Anzahl von Threads benutzt werden, die sich jeweils eigenständig auf die Suche nach einem brauchbaren Ergebnis machen.

Hat ein Thread ein Ergebnis gefunden, so soll das Ergebnis gespeichert werden und alle anderen Threads beenden ihre Berechnungen. Die Main-Funktion soll dabei solange anhalten, bis ein brauchbares Ergebnis von einem Thread generiert wurde.

Hier mein aktueller Code:

https://pastebin.com/g0muSaww

1. Frage: ist der Code so brauchbar/in Ordnung?
2. Frage: das Programm macht im Prinzip schon was es soll, allerdings läuft in der main-funktion die Anweisung einfach weiter, wie kann ich der Main-Funktion verklickern, dass er warten soll bis "doStuff" fertig ist?

Ich hab in dem Code die Mathematik heraus genommen und durch ein simples "suche mir die Zahl 10 heraus" ersetzt.


Vielen Dank schon mal für eure Hilfe :-)
 
1. Frage: ist der Code so brauchbar/in Ordnung?

Eigentlich nicht.
Bei Concurrency ist es wichtig das du geteilte Variablen/Speicher schuetzt.
In diesem Fall ist es nur ein int (test) und sollte kein Probleme machen, dennoch solltest du dir das
direkt angewöhnen.
Schutz erreichst du durch mutex/semaphoren/atomic.



2. Frage: das Programm macht im Prinzip schon was es soll, allerdings läuft in der main-funktion die Anweisung einfach weiter, wie kann ich der Main-Funktion verklickern, dass er warten soll bis "doStuff" fertig ist?

Das erreicht man ueber verschiedene Wege.
Der einfachste ist du wartest auf std eingabe.

Besser ist es mit einer Condition, welche ueber eine int Variable und Mutex geschuetzt ist.

Hier als Pseudo Code:
Code:
main
{
  doStuff();

  while( ready_cnt < availableProcessors() )
  {
    Condition.wait();
  }
  // ready_cnt >= availableProcessors() .. d.h jeder Thread/Task ist fertig
}

//....

run
{
  // ...
  ready_cnt++;
  Condition.notify(); // wecke main thread...
}

Wobei ready_cnt ein atomic int ist.

siehe dazu auch: https://baptiste-wicht.com/posts/2010/09/java-concurrency-part-5-monitors-locks-and-conditions.html
 
Zuletzt bearbeitet:
Danke erstmal für die Antwort!

Es reicht also nicht aus, wenn ich mit synchronize eine geteilte Variable beim bearbeiten schütze?

Okay die Conditions werd ich mir mal anschauen, danke!
 
synchronized habe ich uebersehen.

Da kann ich leider nicht weiterhelfen, weiss nicht was das bedeutet.
(Bin kein Java Programmierer)

Lies das am besten mal in der Doku nach.
Falls es genau das tut was man mit atomics bzw. locks erreichen will, dann reicht es aus.
 
mit synchronized sperrst du den Zugriff anderer Threads auf die Methode/Variable solange bis der eine Thread damit fertig ist, ich denke das ist genau das was du meinst :-)

Aber vielen Dank auf jeden Fall, der Link zu der Page scheint auch im größeren Stil sehr informativ zu sein!
 
  • auf dem ExecutorService brauchst du awaitTermination(). shutdown() beendet lt. dokumentation zuvor eingerichte tasks sofort, ohne darauf zu warten dass sie durchlaufen.
  • zu dem "static Boolean found" muss noch ein volatile dazu. das bewirkt, dass sie nicht thread-lokal gecached wird, wodurch es z.b. dazu kommen koennte, dass, obwohl die statische variable laengst auf true gesetzt ist, eine thread-lokale gecachete version noch false ist. das funktioniert dann im prinzip so, als waere bei jedem iterationscheck (while(!found)) ein synchronized-block um diesen wahrheitstest.
  • das mit der statischen variable gefaellt mir nicht soo sehr. ein sinnvolleres pattern waere es, jedem task eine found-variable zu geben und die dann durch den findenden thread in den anderen threads umzulegen. kapselung und so.
 
Am einfachsten ist es, Du verwendest anstatt eines Runnable ein Callable. Der Vorteil ist, dass beim Callable bereits ein Rückgabewert vorgesehen ist und das ExecutorService Dir die ganze Synchronisationsarbeit für den Erhalt dieses Rückgabewerts bereits abnimmt. Außerdem bietet das ExecutorService bereits Methoden an, um das Ergebnis mehrerer Callable-Instanzen auszuwerten. Der Code in doStuff() sieht damit so aus:

Code:
List<Callable<Integer>> tasks = new ArrayList<>(threadCount);
for (int i = 0; i < threadCount; i++)
   tasks.add(new Task());
// startet alle Tasks und retourniert das erste Ergebnis
Integer result = Executors.newFixedThreadPool(threadCount).invokeAny(tasks);
executorService.shutDown();
System.out.println("Das Resultat lautet " + result);

Das Task wird auch wesentlich simpler und sieht so aus:

Code:
static class Task implements Callable<Integer> {

  static volatile boolean found;

  @Override
  public Integer call() { 
    while (!found) {
      if (ran.nextInt(1000000000 - 1) + 1 == 10) {
        found = true;
        return 10;
      }  
    }
  }
}
 
Schau Dir doch mal das fork/join-Framework an, das es seit Java 7 gibt. Damit kannst Du Dir den ganzen Overhead mit Thread-Erzeugung und ThreadPool sparen. Ein guter Einstieg wäre hier: An Introduction to the Fork / Join Framework
 
Zurück
Oben