Programmiertechnik/Dateien sperren/in Java

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

Java bietet seit Version 1.4 Dateisperren über das java.nio-Interface an. Dieses nutzt unter UNIX-artigen Betriebssystemen fcntl(2) und unter Windows LockFileEx. Die Klasse FileChannel besitzt die Methode lock, mit deren Hilfe man Dateien sperren kann. Diese Methode gibt ein Objekt der Klasse FileLock zurück. Das Objekt muss man jedoch nur in einer Variable speichern, wenn man plant, die Sperre manuell wieder aufzuheben – das Schließen der Datei reicht bereits aus, um die Sperre automatisch aufzuheben. Ein Objekt der FileChannel-Klasse erhält man über die getChannel-Methode diverser Klassen (RandomAccessFile, FileInputStream, etc.). Die Methode lock besitzt folgende Signaturen:

FileLock FileChannel.lock ();
FileLock FileChannel.lock (long position, long size, boolean shared);

Ein direkter Aufruf von lock () ist äquivalent zu einem Aufruf von lock (0, Long.MAX_VALUE, false). Die Funktion erwartet folgende Parameter:

Parameter Erläuterung
long position Der Beginn des Bereichs, der gesperrt werden soll.
long size Die Länge des Bereichs, der gesperrt werden soll.
bool shared Falls die Sperre exklusiv sein soll, false, sonst true.

Die Methode gibt im Erfolgsfall ein FileLock-Objekt zurück. Im Fehlerfall wird eine Exception geworfen.

Die Klassen FileLock und FileChannel finden sich im Paket java.nio.channels, das jedoch nur importiert werden muss, wenn man explizit Objekte dieser Klassen speichern will.

Im folgenden wird auf den Dateizugriff in Java selbst nicht näher eingegagen (vor allem, da es auf Grund der reichhaltigen Anzahl verschiedener Objekte sehr viele unterschiedliche Möglichkeiten gibt, den Zugriff konkret zu realisieren), sondern nur auf das Locking selbst.

Beachten Sie: Dateisperren eignen sich in Java in der Regel nicht dafür, in Servlets eingesetzt zu werden, um Dateien zwischen verschiedenen Requests zu isolieren! Dies hat den Hintergrund, dass bei Servlets die Servlet-Klasse ein einziges Mal instanziert wird und die Requests lediglich auf verschiedene Threads der gleichen virtuellen Maschine verteilt werden. Weitere Informationen hierzu sind im Abschnitt Threads zu finden.


Eine Datei auslesen

Eine Datei auslesen
import java.io.*;

public class readctr {
  public static void main (String [] args) throws Exception {
    FileInputStream is = new FileInputStream ("counter.txt");
    is.getChannel ().lock (0, Long.MAX_VALUE, true);
    BufferedReader reader = new BufferedReader (new InputStreamReader (is));
    long zaehler = Long.valueOf (reader.readLine ());
    is.close ();
    System.out.println ("Aktueller Zaehlerstand: " + zaehler);
  }
}

Das obige Beispiel zeigt, wie man Inhalt einer Datei durch Sperren geschützt auslesen kann. Zuerst wird ein FileInputStream erzeugt, damit die Datei eingelesen werden kann. Dieser besitzt die Methode getChannel, die ein FileChannel-Objekt zurückgibt, bei dem man die lock-Methode aufrufen kann. Da hier die gesamte Datei gesperrt werden soll, wird hier ein Bereich von 0 bis Long.MAX_VALUE angegeben, damit ist die Datei immer vollständig gesperrt, egal, wie groß sie wird (mit größeren Dateien als Long.MAX_VALUE kann Java sowieso nicht umgehen und die Speicherkapazitäten haben diese Grenze auch noch nicht erreicht). Danach wird die Datei ausgelesen, geschlossen und der Zählerstand ausgegeben.

Wichtig an diesem Beispiel ist, dass der BufferedReader erst nach dem Sperren angelegt wird, damit Inhalt der Datei auf keinen Fall in irgend einen Puffer gelesen wird, bevor die Sperre etabliert ist.

Beachten Sie: In diesem Code ist keine Fehlerbehandlung vorhanden, da alle Methoden unter Java Exceptions werfen, die bei diesen trivialen Beispielen zum sofortigen Ende des Programms führen, was hier auch gewünscht ist (vgl. die Perl/PHP-Beispiele). In anderen Zusammenhängen sollten die Exceptions jedoch sinnvoll abgefangen werden!


Ausschließliches Beschreiben einer Datei

Ausschließliches Beschreiben einer Datei
import java.io.*;

public class resetctr {
  public static void main (String [] args) throws Exception {
    RandomAccessFile f = new RandomAccessFile ("counter.txt", "rw");
    f.getChannel ().lock ();
    FileDescriptor fd = f.getFD ();
    FileWriter writer = new FileWriter (fd);
    f.setLength (0);
    writer.write ("0");
    writer.close ();
    System.out.println ("Zaehler wurde zurueckgesetzt.");
  }
}

Das obige Beispiel zeigt, wie man Inhalt einer Datei durch Sperren geschützt neu schreiben kann, wenn man kein Interesse am bestehenden Inhalt hat. Wie bereits in der Einleitung erwähnt, ist es ein Fehler, einfach einen FileOutputStream zu öffnen, denn dieser zerstört den Dateiinhalt beim Öffnen der Datei. Der richtige Weg ist es, die Datei über das Erzeugen eines RandomAccessFile-Objekts zu öffnen – und dies im Lese- und Schreibmodus. Ein RandomAccessFile-Objekt besitzt ebenfalls die Methode getChannel, mit deren Hilfe die Datei dann gesperrt werden kann. Die Sperre soll in diesem Fall exklusiv sein, daher kann die Kurzform von lock () verwendet werden. Nach dem Sperren der Datei kann der Inhalt mit der setLength-Methode der RandomAccessFile-Klasse gelöscht werden; der Dateizeiger steht sowieso am Anfang, daher muss dieser nicht neu positioniert werden.

Es ist problemlos möglich, anstelle eines FileWriter-Objekts auch einen FileOutputStream zu nutzen, dieser bietet auch einen Konstruktor an, der einen FileDescriptor akzeptiert.


Lesen und Schreiben in eine Datei

"Lesen und schreiben in eine Datei
import java.io.*;

public class incctr {
  public static void main (String [] args) throws Exception {
    RandomAccessFile f = new RandomAccessFile ("counter.txt", "rw");
    f.getChannel ().lock ();
    FileDescriptor fd = f.getFD ();
    BufferedReader reader = new BufferedReader (new FileReader (fd));
    long zaehler = Long.valueOf (reader.readLine ());
    FileWriter writer = new FileWriter (fd);
    f.seek (0);
    f.setLength (0);
    writer.write (new Long (zaehler + 1).toString ());
    writer.close ();
    System.out.println ("Zaehler wurde um eins erhoeht und betraegt jetzt: " + (zaehler + 1));
  }
}

In diesem Beispiel geht es darum, eine Datei zuerst auszulesen, auf den Inhalt zu reagieren und den Inhalt danach wieder in die Datei zurückzuschreiben. Wichtig ist hierbei, dass die Datei über den Verlauf der gesamten Operation exklusiv gesperrt bleibt, damit keine race conditions auftreten. Die hier dargestellte Methode ist eine Abwandlung der ersten Methode zum Schreiben der Datei. Allerdings wird hier vor dem Löschen des Dateiinhalts die Datei zuerst gelesen. Die Zeile mit dem seek-Methodenaufruf ist notwendig, um den Positionszeiger der Datei nach dem Lesen wieder an den Anfang zu setzen. Damit hier die Datei von vorne herein im richtigen Modus (zum Lesen und Schreiben) geöffnet wird, wird auch hier wieder ein RandomAccessFile-Objekt instanziert, über das dann die FileReader- und FileWriter-Objekte erzeugt werden.

Weblinks