homedukeOOP mit Java: Synchronisation von Threads Prof. Dr. Uwe Schmidt FH Wedel

Synchronisation von Threads

weiter

weiter

Synchronisation

Zugriff auf gemeinsame Resourcen
in Java: Zugriff auf Objekte

Beispiel:

class Name {
  String firstname;
  String surname;
 
  public
  void setName(String firstname,
               String surname) {
    this.firstname = firstname;
    this.surname   = surname;
  }
 
  public
  String getName() {
    return
      firstname + " " + surname;
  }
}

Problem:

Name n;
 
//-------------
 
// thread 1:
  ...
  n.setName("Charlie","Chaplin");
  ...
  n.setName("Jerry","Lewis");
  ...
 
//-------------
 
// thread 2:
 
  ... n.getName() ...

mögliche Resultate für n.getName():

"Charlie Chaplin"
"Jerry Lewis"
"Jerry Chaplin"
"Charlie Lewis"

Ursache:

this.firstname = firstname;
this.surname   = surname;

und

return
  firstname + " " + surname;

sind unterbrechbare (teilbare) Operationen

weiter

weiter

Lösung: Monitore

Synchronisation mit Monitoren:
Jedes Objekt wird zu einem Monitor gemacht.

class SyncName {
  String firstname;
  String surname;
 
  public
  synchronized
  void setName(String firstname,
               String surname) {
      this.firstname = firstname;
      this.surname   = surname;
  }
 
  public
  synchronized
  String getName() {
      return
          firstname + " " + surname;
  }
}

Naiver Implementierungsversuch.
NUR zur Veranschaulichung der Bedeutung von synchronized,
die Semantik ist in Wirklichkeit aber komplexer.

class SyncName {
    String firstname;
    String surname;
 
    boolean busy = false;
 
  public
  void setName(String firstname,
               String surname) {
      while (busy) {}
      busy = true;
 
      this.firstname = firstname;
      this.surname   = surname;
 
      busy = false;
  }
 
  public
  String getName() {
      String res;
      while (busy) {}
      busy = true;
 
      res = firstname + " " + surname;
 
      busy = false;
 
      return
          res;
  }
}

setName und getName sind unteilbare Operationen geworden.
Solange eine synchronized-Routine ausgeführt wird, kann keine andere synchronized-Routine auf diesem Objekt ausgeführt werden.

Syntax: synchronized modifier

    ...
    synchronized
    Type method(...) {
          // der gesammte Rumpf ist synchronisiert
          // mit this
      ...
    }

oder synchronized Anweisung

    ...
    synchronized ( e ) {
          // synchronized statement
          // dieser Block wird synchronisiert
          // mit dem Objekt, das durch e referenziert wird
      ...
    }

ein ausführbares Beispiel

weiter

weiter

Synchronisation mit wait und notify

Problem:

Zugang zum Monitor abhängig vom Zustand des Monitors

Beispiel:

Verbraucher-Prozess möchte Daten aus einem Puffer abholen, der Puffer ist aber noch nicht vom Erzeuger-Prozess gefüllt

Lösung:

wait() gibt Monitor für andere Prozesse frei
notify() stößt andere Prozesse, die ein wait() ausgeführt haben, wieder an.

wait() und notify() sind in Object definiert, also für alle Objekte vorhanden.

Klassisches Beispiel:

Erzeuger-Verbraucher mit Puffer als gemeinsamer Resource

class Buffer {
 
  // the two buffer variables
  private
  boolean empty = true;
 
  private
  int     value;
 
  //--------------------
  // the producer routine
 
  public
  synchronized
  void put(int v) {
 
    // using if in this context is an error
    // example not yet complete !!!
 
    if ( ! empty ) {
      // producer must wait until buffer empty
      try {
        wait();
      }
      catch ( InterruptedException e ) { }
    }
 
    // here the buffer is empty, fill it
    value = v;
    empty = false;
 
    // notify waiting consumer
    notify();
  }
 
  //--------------------
  // the consumer routine
 
  public
  synchronized
  int get() {
    int v;
 
    // using if in this context is an error
    // example not yet complete !!!
 
    if ( empty ) {
      // consumer must wait until buffer is full
      try {
        wait();
      }
      catch ( InterruptedException e ) { }
    }
 
    // here the buffer is full, empty it
    v     = value;
    empty = true;
 
    // notify waiting producer
    notify();
 
    return v;
  }
}

ein ausführbares Beispiel

weiter

weiter

Verklemmungen, deadlocks

Problem:

Zwei threads und zwei Resourcen,
jeder thread befindet sich in einem kritischen Abschnitt einer Resource und möchte auf die jeweils andere Resource zugreifen

Beispiel:

class Deadlock {
 
    Object lock1 = new SomeClass();
    Object lock2 = new SomeClass();
 
    public void foo() {
        synchronized (lock1) {
            synchronized (lock2) {
                // ...
            }
        }
    }
 
    public void bar() {
        synchronized (lock2) {
            synchronized (lock1) {
                // ...
            }
        }
    }
}
merke die Erkennung von deadlocks ist nicht immmer so einfach!!!

2. Beispiel: Zyklische Struktur mit geschachtelten Monitoraufrufen

class X {
    Y y1;
 
    public
    synchronized
    void setY1(Y y) {
        y1 = y;
    }
 
    public
    synchronized
    void foo() {
        y1.bar();
    }
 
    public
    synchronized
    void bar() {
        // ...
    }
}
 
class Y {
    X x1;
 
    public
    synchronized
    void setX1(X x) {
        x1 = x;
    }
 
    public
    synchronized
    void foo() {
        x1.bar();
    }
 
    public
    synchronized
    void bar() {
        // ...
    }
}
 
public
class Deadlock2 {
 
    X x;
    Y y;
 
    public Deadlock2() {
        x = new X();
        y = new Y();
 
        x.setY1(y);
        y.setX1(x);
 
        t1 = new MyThread(x,y);
        t2 = new MyThread(x,y);
 
        t1.start()// --> x.foo()
        t2.start()// --> y.foo()
    }
}
merke Geschachtelte Monitor-Aufrufe für verschiedene Objekte bergen immer die Gefahr von Verklemmungen.
merke Die deadlock-Freiheit kann nicht durch Testen gezeigt werden.
merke Die deadlock-Freiheit kann nur durch systematische Programm-Konstruktion erreicht werden.
merke nie synchronized-Methoden von anderen Objekten aus synchronized-Methoden aufrufen.
merke Deadlocks erfordern immer mindestens zwei Threads. Der Grund hierfür liegt darin, dass ein Thread, der in einen Monitor eingetreten ist, wieder synchronized Blöcke dieses Monitors betreten darf (Java Language Specification: Chapter 17 Threads and Locks: 17.5 Rules about Locks).

Die beiden folgenden Programmfragmente erzeugen also, wie in einer früheren Version dieser Seite fälschlicherweise dargestellt, keinen Deadlock, da hier der gleicheThread mehrfach einen Monitor betritt. Dieses ist explizit erlaubt.

class X {
  synchronized void f() {
    // no deadlock: same monitor
    f();
  }
}

oder

class X {
  void f() {
    synchronized (this) {
      synchronized (this) {
        // no deadlock: same monitor
      }
    }
  }
}
weiter

weiter

Semaphore in Java

Semaphore sind in eine Richtung beschränkte Zähler mit Synchronisation der zählenden Prozesse.

Beispiel:

//--------------------
 
public
class CountingSemaphore {
    private int count;
 
    public
    CountingSemaphore(int initialCount) {
        count = initialCount;
    }
 
    public
    synchronized
    void P() {
                // if would be an error !!!
        while ( count <= 0 ) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        --count;
    }
 
    public
    synchronized
    void V() {
        ++count;
        notify();
    }
}
merke zyklische Abhängigkeiten in den Monitoren
merke wait Aufruf in einer while-Scheife,
sonst Gefahr von Inkonsistenzen
spin locks
merke defensivere Strategie:
notify wird durch notifyAll ersetzt
weiter

weiter

Probleme mit stop() und destroy()

Problem:

Abbrechen eines threads während dieser sich mit der Ausführung in einen Monitor befindet

Beispiel:

class X {
    // ...
 
    synchronized void f() {
        // ...
 
        Thread.currentThread().stop();
    }
}

Thread

merke stop() ist deprecated (missbilligt) ab JDK 1.2
weiter

Letzte Änderung: 15.06.2009
© Prof. Dr. Uwe Schmidt
Prof. Dr. Uwe Schmidt FH Wedel