24.6 Einkaufen - ein Beispiel
 
  Die Modellierung des schon in 24.5 beschriebene Szenarios lässt sich am einfachsten in einem Diagramm darstellen. Wir benutzen dazu die sog. UML-Notation, auf die wir später noch ausführlicher eingehen werden.
UML = Unified Modeling Language

 

Diagramm lesen Die blauen Kästen symbolisieren Klassen, die wir selber schreiben, ihre Namen stehen in der ersten Ableitungen. Die unteren Abteilungen werden wir später benutzen. Die Klasse Thread ist  rötlich gefärbt, um anzudeuten, dass wir sie nicht implementieren. Die Klasse Kunde soll von der Klasse Thread erben. Dies wird in der UML-Notation durch einen Pfeil dargestellt, der an seinem einen Ende ein ungefülltes Dreieck hat. Der Pfeil zeigt zu der Klasse, von der geerbt werden soll. Neben der Klasse Kunde gibt es noch die Klasse Einkaufswagen. Da ein Kunde einen Einkaufswagen (vorübergehen) in Besitz nehmen kann, ihn also benutzt, zeichnen wir einen Aggregationspfeil zwischen die beiden. Der Aggratiopnspfeil hat einen leeren Rhombus 'bei' der Klasse, die aggregiert. Die Zahlen 1,0 bei der Klasse Einkaufswagen sagen, dass ein Einkaufswagenobjekt genau o oder 1-mal aggregiert werden kann. Der Wagen steht also im Depot, oder wird von genau einem Kunden benutzt. Die Zahlen 0,1 bei der Klasse Kunde sagen, dass ein Kunde entweder keinen oder genau einen Einkaufswagen in Gebrauch hat.
 
Download: Einkaufswagen. java
public class Einkaufswagen {
  public void istInGebrauch(){
    System.out.println(Thread.currentThread().getName()
                 +" geht Einkaufen!");
    try {
      int dauer = (int)(Math.random()*5000);
      Thread.sleep(dauer);
      System.out.println("Dauer des Einkaufs von "
                 +Thread.currentThread().getName()+": "+dauer);
    } catch (InterruptedException e) {
      //
    }
    System.out.println(Thread.currentThread().getName()
                 +" ist fertig");
  }
}
Bemerkungen Die Klasse Einkaufswagen besitzt nur eine Methode: istInGebrauch(). In ihr wird der Gebrauch des Einkaufswagens mit Hilfe der sleep()-Methode simuliert. In unserem Fall bestimmt eine Zufallszahl zwischen 0 und 5000 die Dauer des Einkaufs. Beim Ein- in bzw. Ausstieg aus der Methode wird der Name des Threads, der auf die Methode zugreift ermittelt und ausgegeben.
 
Download:
Kunde.java
public class Kunde extends Thread {
  
  private Einkaufswagen wagen;
  
  public Kunde(String name, Einkaufswagen wagen) {
    super(name);
    this.wagen = wagen;
  }

  public void run() {
    wagen.istInGebrauch();
  }
}
Bemerkungen Die Klasse Kunde aggregiert einen Einkaufswagen, genauer: das Einkaufswagen-Objekt wagen wird einem Kunden-Objekt bei seiner Erzeugen zugewiesen wird. Die von Thread geerbte run()-Methode wird überschrieben und ruft für den aggregierten wagen die Methode istInGebrauch() auf.
 
  Schließlich benötigen wir noch eine Klasse, die das ganze Szenario, das heißt Kunden und Einkaufswagen verwaltet: Es ist dies die Klasse Einkaufen. Sie enthält die main()-Methode. Ihre Rolle wird im UML-Klassendiagramm deutlich. Sie aggregiert die Klasse Kunde und Einkaufswagen. Die beiden 1-en bei der Klasse Einkaufen sagen, dass ein Einkaufswagen- und ein Kunden-Objekt nur zu einer Instanz von Einkaufen gehören. Umgekehrt kann Einkaufen im Prinzip beliebig  - genauer: zwischen null und unendlich viele - Kunden- und Einkaufswagen-Objekte verwalten. Die wird durch 0..* ausgedrückt.
 
Download:
Einkaufen.java
public class Einkaufen {
  
  // Gemeinsam genutzte Resource Einkaufswagen
  static Einkaufswagen wagen = new Einkaufswagen();
  // Thread's benutzen die Resource Einkaufswagen gemeinsam
  static Kunde fritz = new Kunde("Fritz", wagen);
  static Kunde annerose = new Kunde("Annerose", wagen);

  public static void main(String[] args) {
    fritz.start();    //Fritz geht Einkaufen
    annerose.start(); //Annerose geht Einkaufen
  }
}
Bemerkungen Um unser eigentliche Problem, die Vermeidung von Konflikten bei parallelen Prozessen zu verdeutlichen erzeugen wir nur einen wagen, den wir den beiden Kunden fritz und annerose übergeben und damit Einkaufen schicken.
 
Ausgabe Fritz geht Einkaufen!
Annerose geht Einkaufen!
Dauer des Einkaufs von Annerose: 2946
Annerose ist fertig
Dauer des Einkaufs von Fritz: 3188
Fritz ist fertig

 
  Der Konflikt wird deutlich. Annerose geht schon Einkaufen, bevor Fritz mit dem Wagen zurück ist. Ja sie ist schon vor Fritz mit ihrem Einkauf fertig. Dass Annerose schon vor Fritz zurückkehrt ist nicht immer der Fall, da die Einkaufsdauer über einen Zufallsgenerator gesteuert wird.
   
Lösungs-variante 1
 

Download:
Kunde.java

Wir verändern Kunde (die beiden anderen Klassen bleiben unverändert):
public class Kunde extends Thread {
  
  private Einkaufswagen wagen;
  
  public Kunde(String name, Einkaufswagen wagen) {
    super(name);
    this.wagen = wagen;
  }

  public void run() {
    synchronized (wagen) {
      wagen.istInGebrauch();
    }
  }
}
  Der erste Thread holt sich für das Objekt wagen eine Sperre. Das bedeutet, dass solang dieser Thread die Sperre besitzt kein weiterer Thread auf das Objekt wagen zugreifen kann.
 
Ausgabe Fritz geht Einkaufen!
Dauer des Einkaufs von Fritz: 4239
Fritz ist fertig
Annerose geht Einkaufen!
Dauer des Einkaufs von Annerose: 914
Annerose ist fertig

 
Lösungs-variante 2

Download:
Einkaufswagen. java

Wir synchrionisieren die Methode istInGebrauch(), d.h. ein Objekte das über einen Thread auf diese Methode zugreifen, besitzen eine Sperre, weitere Threads werden am Zugriff gehindert. Die Klasse Kunde bekommt wieder ihre alte Gestalt.
public class Einkaufswagen {
  public synchronized void istInGebrauch(){
    System.out.println(Thread.currentThread().getName()
                 +" geht Einkaufen!");
    try {
      int dauer = (int)(Math.random()*5000);
      Thread.sleep(dauer);
      System.out.println("Dauer des Einkaufs von "
                 +Thread.currentThread().getName()+": "+dauer);
    } catch (InterruptedException e) {
      //
    }
    System.out.println(Thread.currentThread().getName()
                 +" ist fertig");
  }
}
Ausgabe Der Effekt ist derselbe. Die Ausgabe unterscheidet sioch zur ersten Variante nur in den Einkaufszeiten.
 
  Fritz geht Einkaufen!
Dauer des Einkaufs von Fritz: 3609
Fritz ist fertig
Annerose geht Einkaufen!
Dauer des Einkaufs von Annerose: 3962
Annerose ist fertig
 
Probleme beim Locking (Sperren) Selbst erfahrenen Programmieren passiert es immer wieder, dass sie sogenannte Dead-Locks produzieren. Dead-Locks sind Situationen, in denen ein Thread darauf wartet, dass ein anderer Thread etwas tut, der seinerseits auf den anderen wartet. Beide Threads werden ausgesetzt, es passiert nichts mehr. Vergleichbar ist dies mit der Situation, die im Bild dargestellt ist. Damit einer der beiden schreiben kann, benötigt er Papier und Stift. Das was der eine schon hat, wird er dem anderen nicht geben. Dead-Locks sind gar nicht so selten. Wenn ein Multithreadprogramm hängen bleibt sind in den meisten Fällen Dead-Locks die Ursache. Das Auffinden und das Beseitigen dieser gegenseitigen Blockaden kostet in der Regel sehr viel Zeit und Mühe.
  Das oben dargestellte Bild stammt aus dem Buch:

Java ist auch eine Insel

Chrsitian Ullenboom:
Java ist auch eine Insel - Programmieren für die Java 2-Plattform in der Version 1.4 inkl. CD
Gebundene Ausgabe
- 1342 Seiten - Galileo Press
Erscheinungsdatum: Mai 2003
Auflage: 3. Aufl.
ISBN: 3898423654
 

zu 24.7 Threading mit Swing
zur Startseite www.pohlig.de  (C) MPohlig 2004