So sperren Sie eine Datei in Java

1. Übersicht

Beim Lesen oder Schreiben von Dateien müssen wir sicherstellen, dass die richtigen Dateisperrmechanismen vorhanden sind. Dies stellt die Datenintegrität in gleichzeitigen E / A-basierten Anwendungen sicher.

In diesem Tutorial werden verschiedene Ansätze vorgestellt, um dies mithilfe der Java NIO-Bibliothek zu erreichen .

2. Einführung in Dateisperren

Im Allgemeinen gibt es zwei Arten von Schlössern :

    • Exklusive Sperren - auch als Schreibsperren bezeichnet
    • Gemeinsame Sperren - auch als Lesesperren bezeichnet

Einfach ausgedrückt verhindert eine exklusive Sperre alle anderen Vorgänge - einschließlich Lesevorgänge -, während ein Schreibvorgang abgeschlossen wird.

Im Gegensatz dazu ermöglicht eine gemeinsame Sperre, dass mehrere Prozesse gleichzeitig gelesen werden. Der Sinn einer Lesesperre besteht darin, die Erfassung einer Schreibsperre durch einen anderen Prozess zu verhindern. Normalerweise sollte eine Datei in einem konsistenten Zustand tatsächlich von jedem Prozess gelesen werden können.

Im nächsten Abschnitt werden wir sehen, wie Java mit diesen Arten von Sperren umgeht.

3. Dateisperren in Java

Die Java NIO-Bibliothek ermöglicht das Sperren von Dateien auf Betriebssystemebene. Zu diesem Zweck dienen die Methoden lock () und tryLock () eines FileChannel .

Wir können einen FileChannel entweder über einen FileInputStream , einen FileOutputStream oder eine RandomAccessFile erstellen . Alle drei haben eine getChannel () -Methode, die einen FileChannel zurückgibt .

Alternativ können wir einen erstellen Filechannel direkt über die statische offene Methode:

try (FileChannel channel = FileChannel.open(path, openOptions)) { // write to the channel }

Als Nächstes werden verschiedene Optionen zum Abrufen exklusiver und gemeinsam genutzter Sperren in Java überprüft. Weitere Informationen zu Dateikanälen finden Sie in unserem Tutorial zu Java FileChannel.

4. Exklusive Schlösser

Wie wir bereits erfahren haben, können wir beim Schreiben in eine Datei verhindern, dass andere Prozesse sie lesen oder in sie schreiben, indem wir eine exklusive Sperre verwenden .

Wir erhalten exklusive Sperren, indem wir lock () oder tryLock () für die FileChannel- Klasse aufrufen . Wir können auch ihre überladenen Methoden verwenden:

  • Sperre (Long Position, Long Size, Boolean Shared)
  • tryLock (Long Position, Long Size, Boolean Shared)

In diesen Fällen muss der gemeinsam genutzte Parameter auf false gesetzt werden .

Um eine exklusive Sperre zu erhalten, müssen wir einen beschreibbaren FileChannel verwenden . Wir können es mit den getChannel () -Methoden eines FileOutputStream oder einer RandomAccessFile erstellen . Alternativ kann , wie bereits erwähnt, können wir die statische verwenden offene Methode der Filechannel - Klasse. Wir müssen lediglich das zweite Argument auf StandardOpenOption.APPEND setzen :

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.APPEND)) { // write to channel }

4.1. Exklusive Sperren mit einem FileOutputStream

Ein Filechannel von einer erstellten Outputstream ist beschreibbar. Wir können daher ein exklusives Schloss erwerben:

try (FileOutputStream fileOutputStream = new FileOutputStream("/tmp/testfile.txt"); FileChannel channel = fileOutputStream.getChannel(); FileLock lock = channel.lock()) { // write to the channel }

Hier blockiert channel.lock () entweder, bis es eine Sperre erhält, oder es wird eine Ausnahme ausgelöst. Wenn beispielsweise die angegebene Region bereits gesperrt ist, wird eine OverlappingFileLockException ausgelöst. Eine vollständige Liste möglicher Ausnahmen finden Sie im Javadoc.

Wir können auch eine nicht blockierende Sperre mit channel.tryLock () durchführen . Wenn keine Sperre angezeigt wird, weil ein anderes Programm eine überlappende Sperre enthält, wird null zurückgegeben . Wenn dies aus einem anderen Grund nicht der Fall ist, wird eine entsprechende Ausnahme ausgelöst.

4.2. Exklusive Sperren mit einer RandomAccessFile

Bei einer RandomAccessFile müssen wir Flags für den zweiten Parameter des Konstruktors setzen.

Hier öffnen wir die Datei mit Lese- und Schreibberechtigungen:

try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "rw"); FileChannel channel = file.getChannel(); FileLock lock = channel.lock()) { // write to the channel } 

Wenn wir die Datei im schreibgeschützten Modus öffnen und versuchen, in ihren Kanal zu schreiben, wird eine NonWritableChannelException ausgelöst .

4.3. Exklusive Sperren erfordern einen beschreibbaren FileChannel

Wie bereits erwähnt, benötigen exklusive Sperren einen beschreibbaren Kanal. Daher können wir keine exklusive Sperre über einen FileChannel erhalten, der aus einem FileInputStream erstellt wurde :

Path path = Files.createTempFile("foo","txt"); Logger log = LoggerFactory.getLogger(this.getClass()); try (FileInputStream fis = new FileInputStream(path.toFile()); FileLock lock = fis.getChannel().lock()) { // unreachable code } catch (NonWritableChannelException e) { // handle exception }

Im obigen Beispiel löst die lock () -Methode eine NonWritableChannelException aus . Dies liegt in der Tat daran, dass wir getChannel in einem FileInputStream aufrufen , der einen schreibgeschützten Kanal erstellt.

Dieses Beispiel soll nur zeigen, dass wir nicht in einen nicht beschreibbaren Kanal schreiben können. In einem realen Szenario würden wir die Ausnahme nicht abfangen und erneut auslösen.

5. Gemeinsame Schlösser

Denken Sie daran, gemeinsame Sperren werden auch als Lesesperren. Um eine Lesesperre zu erhalten, müssen wir daher einen lesbaren FileChannel verwenden .

Ein solcher FileChannel kann durch Aufrufen der getChannel () -Methode in einem FileInputStream oder einer RandomAccessFile abgerufen werden . Auch hier ist eine weitere Option , die statische verwenden offene Methode der Filechannel - Klasse. In diesem Fall setzen wir das zweite Argument auf StandardOpenOption.READ :

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ); FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) { // read from the channel }

One thing to note here is that we chose to lock the entire file by calling lock(0, Long.MAX_VALUE, true). We could also have locked only a specific region of the file by changing the first two parameters to different values. The third parameter has to be set to true in the case of a shared lock.

To keep things simple, we’ll be locking the whole file in all the examples below, but keep in mind we can always lock a specific region of a file.

5.1. Shared Locks Using a FileInputStream

A FileChannel obtained from a FileInputStream is readable. We can, therefore, get a shared lock:

try (FileInputStream fileInputStream = new FileInputStream("/tmp/testfile.txt"); FileChannel channel = fileInputStream.getChannel(); FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) { // read from the channel }

In the snippet above, the call to lock() on the channel will succeed. That's because a shared lock only requires that the channel is readable. It's the case here since we created it from a FileInputStream.

5.2. Shared Locks Using a RandomAccessFile

This time, we can open the file with just read permissions:

try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "r"); FileChannel channel = file.getChannel(); FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) { // read from the channel }

In this example, we created a RandomAccessFile with read permissions. We can create a readable channel from it and, thus, create a shared lock.

5.3. Shared Locks Require a Readable FileChannel

For that reason, we can't acquire a shared lock through a FileChannel created from a FileOutputStream:

Path path = Files.createTempFile("foo","txt"); try (FileOutputStream fis = new FileOutputStream(path.toFile()); FileLock lock = fis.getChannel().lock(0, Long.MAX_VALUE, true)) { // unreachable code } catch (NonWritableChannelException e) { // handle exception } 

In this example, the call to lock() tries to get a shared lock on a channel created from a FileOutputStream. Such a channel is write-only. It doesn't fulfil the need that the channel has to be readable. This will trigger a NonWritableChannelException.

Again, this snippet is just to demonstrate that we can't read from a non-readable channel.

6. Things to Consider

In practice, using file locks is difficult; the locking mechanisms aren’t portable. We’ll need to craft our locking logic with this in mind.

In POSIX systems, locks are advisory. Different processes reading or writing to a given file must agree on a locking protocol. This will ensure the file’s integrity. The OS itself won’t enforce any locking.

Unter Windows sind Sperren exklusiv, sofern keine Freigabe zulässig ist. Die Erörterung der Vor- oder Nachteile betriebssystemspezifischer Mechanismen liegt außerhalb des Geltungsbereichs dieses Artikels. Es ist jedoch wichtig, diese Nuancen bei der Implementierung eines Verriegelungsmechanismus zu kennen.

7. Fazit

In diesem Tutorial haben wir verschiedene Optionen zum Abrufen von Dateisperren in Java überprüft.

Zunächst verstanden wir die beiden wichtigsten Sperrmechanismen und wie die Java NIO-Bibliothek das Sperren von Dateien erleichtert. Anschließend gingen wir eine Reihe einfacher Beispiele durch, aus denen hervorgeht, dass wir in unseren Anwendungen exklusive und gemeinsam genutzte Sperren erhalten können. Wir haben uns auch die typischen Ausnahmen angesehen, die bei der Arbeit mit Dateisperren auftreten können.

Wie immer ist der Quellcode für die Beispiele auf GitHub verfügbar.