Advanced   Java   Services Locks Back Next Up Home


Locks

Der große Nachteil von synchronized ist, daß ein Thread ALLE synchronized-Methoden sperrt, sobald er eine synchronized-Methode abarbeitet. Das liegt einfach daran, daß es pro Objekt nur einen Lock gibt. Sobald der Monitor diesen einen Lock vergeben hat, kann zwangsläufig kein anderer Thread mehr auf synchronized-Methoden oder Blöcke dieses Objekts zugreifen. Dasselbe gilt auch für statische Methoden, da es auch hier nur einen Monitor pro Klasse gibt. Mit der im Package concurrent eingeführten Klasse Lock kann man sozusagen lokale Locks bzw. Monitore einrichten, die etwa nur eine Methode locken oder auch nur bestimmte Codeteile. Andere Methoden oder Codeteile sind dann von diesem Lock nicht betroffen.


Das Interface   java.util.concurrent.locks.Lock

Die beiden zentralen Methoden sind lock und unlock. Da Lock ein klassisches Interface ist sind alle Methoden abstrakt. Die einzige Implementierung in der API ist bislang die Klasse ReentrantLock. Da Lock aber ein Interface ist, kann man eigene Implementierungen nach speziellen Bedürfnissen schreiben. Die Methoden des Interfaces bieten zudem mehr Flexibilität als das Schlüsselwort synchronized.

java.util.concurrent.locks.Lock
ReturntypName der Methode
voidlock()
Acquires the lock.
voidlockInterruptibly()
Acquires the lock unless the current thread is interrupted.
booleantryLock()
Acquires the lock only if it is free at the time of invocation. Acquires the lock if it is available and returns immediately with the value true. If the lock is not available then this method will return immediately with the value false.
booleantryLock(long time, TimeUnit unit)
Acquires the lock if it is free within the given waiting time and the current thread has not been interrupted.
voidunlock()
Releases the lock.
ConditionnewCondition()
Returns a new Condition instance that is bound to this Lock instance.

Zum Vergleich nochmal das Vorgehen vor Java 5

Object lock = new Object();
// ...
synchronized(lock)
{
   // something that requires synchronized access
}

Anders als bei synchronized muß man hier den Lock selbst aufheben. Die folgende Skizze zeigt die Vorgehensweise. Neu ist hier das try - finally Konstrukt, das in der API ausdrücklich empfohlen wird! Durch finally wird sichergestellt, daß ein Lock auf alle Fälle wieder gelöst wird (Verhinderung von Deadlocks !)

// ab Java 5
Lock lock = ...
...
//Trying to enter the critical section
lock.lock(); // blocks until this thread gets the lock
try
{
   // access the resource protected by this lock
}
finally
{
   lock.unlock(); //releases the lock
}

Einige Möglichkeiten des neuen Konzepts


lockInterruptibly()

Wie die Methode lock() wartet auch lockInterruptibly() auf den Lock. Im Gegensatz zur ersten Methode ist jedoch hier der Wartezustand durch einen Interruptaufruf unterbrechbar.


tryLock()

Mit tryLock kann man falls kein Lock erhältlich ist, in der Zwischenzeit andere Aufgaben erledigen. Im Gegensatz zu lock() blockiert tryLock() in keinem Fall (siehe den API-Auszug in der obigen Tabelle).

Lock lock = ...;

if (lock.tryLock())
{
    try
    {
        // manipulate protected state
    }
    finally
    {
        lock.unlock();
    }
}
else
{
    // perform alternative actions
}

Neu ist hier das try - finally Konstrukt, das in der API ausdrücklich empfohlen wird! Durch finally wird sichergestellt, daß ein Lock auf alle Fälle wieder gelöst wird
(Verhinderung von Deadlocks !)


newCondition()

Mit Hilfe dieser Methode erhält man ein Objekt vom Typ Condition mit dem man das wait-notify-Pattern auf Locks übertragen kann. Die entsprechenden Methoden
heißen hier jetzt await() und signal() und stehen nur über eine Condition zur Verfügung.


Reentranz bei Locks, die Klasse ReentrantLock

Wir haben die folgende Situation als reentrant kennengelernt.

public class AlwaysReentrant
{

  public synchronized first()
  {
    second();
    //do something
    second();
    //do something
  }

  public synchronized second()
  {
    //do something
  }
}

Anders als bei synchronized sind Locks nicht automatisch reentrant. Die wichtigste Implementierung des Interfaces Lock durch die Klasse ReentrantLock ist jedoch, wie der Name schon sagt, reentrant. Die obige Situation für synchronized läßt sich folgendermaßen übertragen.

public class AlsoReentrant
{
  private Lock rLock = new ReentrantLock();

  public void first()
  {
    rLock.lock();
    try
    {
      //..
      second();  //does something
      //..
    }
    finally
    {
      rLock.unlock();
    }
  }

  public void second()
  {
    rLock.lock();
    try
    {
      //..
    }
    finally
    {
      rLock.unlock();
    }
  }
}

Auch hier führt also das erneute Anfordern desselben Locks nicht zu einem Deadlock.

Die Klasse ReentrantLock ist sehr komfortabel ausgestattet. So gibt es etwa mehrer hasX()-Methoden mit denen man abfragen kann ob es Threads gibt, die auf den Lock warten oder ob zu einer Condition mehrere Waiters existieren, auch gibt es Methoden die die Länge einer Warteschlange abschätzen können.


Beispiel, das die Methode getQueueLength() aus ReentrentLock verwendet

Es wird ein Runnable implementiert, das einen Lock erhält und getQueueLength() verwendet

class MyRunnable implements Runnable
{

  private ReentrantLock rLock;

  public MyRunnable(ReentrantLock rLock)
  {
    this.rLock=rLock;
  }

  public void run()
  {
    rLock.lock();
    System.out.println(Thread.currentThread().getName() + " hat den Lock.");
    try
    {
        //Thread.sleep(2000);
        TimeUnit.SECONDS.sleep(2);
        System.out.println("getQueueLength = "+rLock.getQueueLength() + "\n");
    }
    catch (InterruptedException ex)
    {
        ex.printStackTrace();
    }
    finally
    {
      rLock.unlock();
    }
  }
}

In einer main-Klasse werden drei MyRunnables erzeugt und gestartet.

public class ReentrantLockDemo
{
  public static void main(String[] args)
  {
    System.out.println("ReentrantLockDemo");

    ReentrantLock rLock = new ReentrantLock();
    MyRunnable mr = new MyRunnable(rLock);
    Thread th1 = new Thread(mr, "Thread-1");
    Thread th2 = new Thread(mr, "Thread-2");
    Thread th3 = new Thread(mr, "Thread-3");
    th1.start();
    th2.start();
    th3.start();

    System.out.println("end main\n");

  } // end main

}  // end main class

Einige Abläufe

ReentrantLockDemo
end main

Thread-3 hat den Lock.
getQueueLength = 2

Thread-2 hat den Lock.
getQueueLength = 1

Thread-1 hat den Lock.
getQueueLength = 0

------------------------------------------------

ReentrantLockDemo
end main

Thread-2 hat den Lock.
getQueueLength = 2

Thread-1 hat den Lock.
getQueueLength = 1

Thread-3 hat den Lock.
getQueueLength = 0

------------------------------------------------

ReentrantLockDemo
end main

Thread-1 hat den Lock.
getQueueLength = 2

Thread-2 hat den Lock.
getQueueLength = 1

Thread-3 hat den Lock.
getQueueLength = 0
Valid XHTML 1.0 Strict top Back Next Up Home