Advanced   Java   Services ReadWriteLocks


ReadWriteLock

Während die Abarbeitung einer synchronized-Methode alle anderen synchronized-Methoden sperrt, egal ob sie mit der laufenden Methode etwas zu tun haben oder nicht, erlauben ReadWriteLocks ein differenzierteres Vorgehen. Werden Daten nur gelesen, kann man mehreren Threads den Zugriff erlauben. Das Schreiben allerdings muß exklusiv erfolgen. Ein Schreibzugriff kann daher nur erlaubt werden wenn keine anderen Lese- oder Schreiboperationen stattfinden. Genau dieses Modell wird durch die Klasse ReentrantReadWriteLock bereitgestellt. So liefern die beiden Methoden readLock() und writeLock() die notwendigen Locks dafür. Da Lesezugriffe in der Regel viel häufiger sind als Schreibzugriffe kann es bei gleichen Prioritäten evtl. dazukommen, daß Schreibzugriffe nicht oder nur verspätet durchgeführt werden (Starvation). In solchen Fällen muß man dem Schreibvorgang eine höhere Priorität einräumen. Die Bedingungen für das Locking kann man folgendermaßen zusammenfassen.


Noch ein Wort zur Terminologie

Die Threads greifen hier nicht auf Dateien zu, sondern auf Daten (z.Bsp. in einer Liste). Das ist die Standardsituation. Man könnte also statt von read/write etwa auch von shared/exclusive sprechen, jedoch ist die erste Wortwahl die Terminologie, die sich eingebürgert hat.


Beispiel

Eine Klasse Data verwaltet Strings in einer Liste. Dazu stellt sie eine add()- und eine readAll()-Methode zur Verfügung. Mit Hilfe ReadWriteLocks ermöglicht sie Threads die oben dargstellte Form des Zugriffs auf die Daten. Die Anforderung des Locks findet im try-Block statt. Freigegeben wird der Lock jeweils in finally. So wird sichergestellt, daß der Lock zurückgegeben wird.

Die Klasse Data
import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Data
{
   private List<String> names;
   ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

   public Data()
   {
      names = new ArrayList<String>();
   }

   // write
   public void addData(String str)
   {
      try
      {
         rwLock.writeLock().lock();
         System.out.println("addData: " + rwLock.getQueueLength() + " readers or writers waiting");
         // getWriteHoldCount() liefert immer 1
         System.out.println(rwLock.getWriteHoldCount() + " writer is writing: "+ Thread.currentThread().getName());
         names.add(str);
         Thread.sleep(100);
      }
      catch(InterruptedException ex)
      {
      }
      finally
      {
         rwLock.writeLock().unlock();
      }
   }

   //read
   public void readAllData()
   {
      try
      {
         rwLock.readLock().lock();
         System.out.println("readAllData: " + rwLock.getQueueLength() + " readers or writers waiting");
         System.out.println(rwLock.getReadLockCount() + " readers are reading");
         Iterator<String> iter = names.iterator();
         while(iter.hasNext())
         {
            System.out.println(iter.next());  // or write to a file
         }
         Thread.sleep(100);
      }
      catch(InterruptedException ex)
      {
      }
      finally
      {
         rwLock.readLock().unlock();
      }
   }
}

Die beiden folgenden Klassen DataReader und DataWriter sind Runnables, die auf das Dataobjekt zugreifen.

public class DataReader implements Runnable
{
   Data myData;

   public DataReader(Data myData)
   {
      this.myData = myData;
   }

   public void run()
   {
      System.out.println(Thread.currentThread().getName() + " started");
      myData.readAllData();
   }
}
public class DataWriter implements Runnable
{
   Data myData;

   public DataWriter(Data myData)
   {
      this.myData = myData;
   }

   public void run()
   {
      System.out.println(Thread.currentThread().getName() + " started");
      myData.addData("hello");
   }
}

Main legt die Threads an und startet sie.

public class ReadWriteLock01
{
   private static final int READERS = 9;
   private static final int WRITERS = 5;

   public static void main(String[] args)
   {
      DataReader[] readers = new DataReader[READERS];
      DataWriter[] writers = new DataWriter[WRITERS];
      Data data = new Data();
      Thread[] threads = new Thread[READERS + WRITERS];

      for(int i = 0; i < READERS; i++)
      {
         readers[i] = new DataReader(data);
         threads[i] = new Thread(readers[i], "reader-" + i);
      }
      for(int i = 0; i < WRITERS; i++)
      {
         writers[i] = new DataWriter(data);
         threads[i + READERS] = new Thread(writers[i], "writer-" + i);
      }
      // um die Threads ungeordneter zu starten, wird in zwei Durchgängen gestartet
      // etwa zuerst die geraden, dann die ungeraden
      for(int i = 0; i < READERS + WRITERS; i+=2)
      {
         threads[i].start();
      }
      for(int i = 1; i < READERS + WRITERS; i+=2)
      {
         threads[i].start();
      }
   }
}

Ein Ablauf

reader-0 started
reader-6 started
reader-2 started
reader-8 started
reader-4 started
writer-1
readAllData: 1 readers or writers waiting
--- 5 readers are reading
--- Datacount: 0
readAllData: 1 readers or writers waiting
--- 5 readers are reading
readAllData: 1 readers or writers waiting
reader-5 started
readAllData: 0 readers or writers waiting
readAllData: 1 readers or writers waiting
--- 5 readers are reading
--- Datacount: 0
--- 5 readers are reading
--- Datacount: 0
writer-2
--- Datacount: 0
reader-3 started
--- 5 readers are reading
writer-3
writer-4
writer-0
reader-1 started
--- Datacount: 0
reader-7 started
addData: 8 readers or writers waiting
*** 1 writer is writing: writer-1
readAllData: 7 readers or writers waiting
--- 1 readers are reading
--- Datacount: 1
addData: 6 readers or writers waiting
*** 1 writer is writing: writer-2
readAllData: 5 readers or writers waiting
--- 1 readers are reading
--- Datacount: 2
addData: 4 readers or writers waiting
*** 1 writer is writing: writer-3
addData: 3 readers or writers waiting
*** 1 writer is writing: writer-4
addData: 2 readers or writers waiting
*** 1 writer is writing: writer-0
readAllData: 1 readers or writers waiting
--- 2 readers are reading
readAllData: 0 readers or writers waiting
--- 2 readers are reading
--- Datacount: 5
--- Datacount: 5