Wie kann ich eine Datei mit Java sperren (wenn möglich)?

121

Ich habe einen Java-Prozess, der eine Datei mit einem FileReader öffnet. Wie kann ich verhindern, dass ein anderer (Java) Prozess diese Datei öffnet, oder zumindest diesen zweiten Prozess benachrichtigen, dass die Datei bereits geöffnet ist? Wird dadurch der zweite Prozess automatisch eine Ausnahme erhalten, wenn die Datei geöffnet ist (was mein Problem löst), oder muss ich sie im ersten Prozess explizit mit einer Art Flag oder Argument öffnen?

Zu klären:

Ich habe eine Java-App, die einen Ordner auflistet und jede Datei in der Liste zur Verarbeitung öffnet. Es verarbeitet jede Datei nach der anderen. Die Verarbeitung jeder Datei besteht aus dem Lesen und einigen Berechnungen basierend auf dem Inhalt. Sie dauert ca. 2 Minuten. Ich habe auch eine andere Java-App, die das Gleiche tut, aber stattdessen in die Datei schreibt. Ich möchte, dass diese Apps gleichzeitig ausgeführt werden können, damit das Szenario so aussieht. ReadApp listet den Ordner auf und findet die Dateien A, B, C. Es öffnet die Datei A und startet das Lesen. WriteApp listet den Ordner auf und findet die Dateien A, B, C. Es öffnet die Datei A, sieht, dass sie geöffnet ist (ausnahmsweise oder auf welche Weise auch immer) und wechselt zu Datei B. ReadApp beendet Datei A und fährt mit B fort. Es sieht, dass dies der Fall ist ist offen und fährt mit C fort. Es ist entscheidend, dass WriteApp dies nicht tut. t Schreiben, während ReadApp dieselbe Datei liest oder umgekehrt. Sie sind verschiedene Prozesse.

Paralife
quelle
12
Meinen Sie "Prozess" wie in Prozess (zwei JVMs) oder Thread (gleiche JVM). Die Auswirkung auf die Antwort ist von größter Bedeutung.
Stu Thompson
Überprüfen Sie den Beispielcode, der die Lösung hier zeigt: stackoverflow.com/a/58871479/5154619
Davi Cavalcanti

Antworten:

117

FileChannel.lock ist wahrscheinlich das, was Sie wollen.

try (
    FileInputStream in = new FileInputStream(file);
    java.nio.channels.FileLock lock = in.getChannel().lock();
    Reader reader = new InputStreamReader(in, charset)
) {
    ...
}

(Haftungsausschluss: Code nicht kompiliert und schon gar nicht getestet.)

Beachten Sie den Abschnitt "Plattformabhängigkeiten" im API-Dokument für FileLock .

Tom Hawtin - Tackline
quelle
22
Noch wichtiger ist, dass die Sperre für die JVM nicht zum Sperren der Datei für den Zugriff einzelner Threads innerhalb einer einzelnen JVM geeignet ist.
Stu Thompson
11
Sie benötigen einen beschreibbaren Stream (dh FileOutputStream).
Javier
@ Javier Hast du? Ich habe es nicht versucht. Aus den API-Dokumenten springt nichts heraus, was besagt, dass dies eine Voraussetzung ist. FileOutputStreamwird für a nicht viel nützen Reader.
Tom Hawtin - Tackline
18
Ja, ich habe es versucht und es wird ausgelöst NonWritableChannelException, weil lock()versucht wird, eine exklusive Sperre zu erhalten, aber das erfordert Schreibzugriff. Wenn Sie einen Eingabestream haben , können Sie einen verwenden, lock(0L, Long.MAX_VALUE, false)der eine gemeinsame Sperre erhält und nur einen Lesezugriff erfordert. Sie können auch eine RandomAccessFileim Lese- / Schreibmodus geöffnete Funktion verwenden, wenn Sie beim Lesen eine exklusive Sperre wünschen ... dies würde jedoch gleichzeitige Leser verbieten.
Javier
6
@ Javier Ich denke du willst sagen lock(0L, Long.MAX_VALUE, true), nicht lock(0L, Long.MAX_VALUE, false). Das letzte Argument ist boolean shared docs.oracle.com/javase/8/docs/api/java/nio/channels/…
John Sullivan
60

Verwenden Sie nicht die Klassen im java.ioPaket, sondern das java.nioPaket. Letzterer hat eine FileLockKlasse. Sie können eine Sperre auf a anwenden FileChannel.

 try {
        // Get a file channel for the file
        File file = new File("filename");
        FileChannel channel = new RandomAccessFile(file, "rw").getChannel();

        // Use the file channel to create a lock on the file.
        // This method blocks until it can retrieve the lock.
        FileLock lock = channel.lock();

        /*
           use channel.lock OR channel.tryLock();
        */

        // Try acquiring the lock without blocking. This method returns
        // null or throws an exception if the file is already locked.
        try {
            lock = channel.tryLock();
        } catch (OverlappingFileLockException e) {
            // File is already locked in this thread or virtual machine
        }

        // Release the lock - if it is not null!
        if( lock != null ) {
            lock.release();
        }

        // Close the file
        channel.close();
    } catch (Exception e) {
    }
Ayengin
quelle
Übrigens schreibe ich in die Sperrdatei die aktuelle PID von diesem Tipp stackoverflow.com/a/35885/1422630 , also nachdem ich sie auf der neuen Instanz lesen kann!
Wassermann Power
1
Dieser sah gut aus, aber es funktioniert nicht. Ich bekomme jedes Mal die OverlappingFileLockException, auch wenn die Datei nicht einmal vorhanden war
Gavriel
1
Problem wird auftreten, wenn Sie tryLock nach dem Sperren aufrufen, wie es im Beispiel geschrieben ist
Igor Vuković
17

Wenn Sie Java NIO ( JDK 1.4 oder höher ) verwenden können, dann suchen Sie wahrscheinlichjava.nio.channels.FileChannel.lock()

FileChannel.lock ()

KC Baltz
quelle
5
Vielleicht. Kommt darauf an, was OP unter "Prozess" versteht. "Dateisperren werden für die gesamte virtuelle Java-Maschine gehalten. Sie eignen sich nicht zum Steuern des Zugriffs auf eine Datei durch mehrere Threads innerhalb derselben virtuellen Maschine."
Stu Thompson
@Stu: Ich weiß, dass Sie diese Frage vor langer Zeit beantwortet haben, aber ich hoffe, Sie können näher erläutern, was Sie meinen, als Sie sagtenFile locks are held on behalf of the entire Java virtual machine. They are not suitable for controlling access to a file by multiple threads within the same virtual machine
Thang Pham
3
@Harry Er zitiert aus den Dokumenten: download.oracle.com/javase/6/docs/api/java/nio/channels/… Es bedeutet, dass es für Threads unsichtbar ist, aber andere Prozesse beeinflusst.
Artur Czajka
@Harry: Um diesen Nekro-Kommentaren noch mehr hinzuzufügen, stellen Sie sich vor, Sie verwenden Java, um Websites mit Tomcat bereitzustellen. Möglicherweise haben Sie viele Threads, von denen jeder eine Anfrage von einem Webbrowser aus bearbeitet. Sie steuern jedoch alle denselben Mechanismus zum Sperren von Dateien wie zu viele Köche in einer Küche. Eine Anfrage wird möglicherweise in der Mitte einer zweiten abgeschlossen, und plötzlich wurde Ihre Datei "entsperrt", während Sie sich noch in der Mitte von etwas befanden. Dann wird sie möglicherweise durch einen anderen Vorgang wie einen Cronjob gesperrt, und Sie haben Ihre unerwartet verloren sperren und Ihre Anfrage kann nicht beendet werden ...
Darien
5

Dies ist vielleicht nicht das, wonach Sie suchen, aber im Interesse, ein Problem aus einem anderen Blickwinkel zu lösen ...

Sind dies zwei Java-Prozesse, die möglicherweise auf dieselbe Datei in derselben Anwendung zugreifen möchten? Vielleicht können Sie den gesamten Zugriff auf die Datei einfach über eine einzige synchronisierte Methode filtern (oder, noch besser, mit JSR-166 )? Auf diese Weise können Sie den Zugriff auf die Datei steuern und möglicherweise sogar Zugriffsanforderungen in die Warteschlange stellen.

pkaeding
quelle
3
Zwei Prozesse können keine Synchronisation verwenden, nur zwei Threads im selben Prozess.
Marquis von Lorne
3

Verwenden Sie eine RandomAccessFile, rufen Sie den Kanal ab und rufen Sie lock () auf. Der von Eingabe- oder Ausgabestreams bereitgestellte Kanal verfügt nicht über ausreichende Berechtigungen, um ordnungsgemäß zu sperren. Rufen Sie im finally-Block unbedingt lock () auf (das Schließen der Datei hebt die Sperre nicht unbedingt auf).

Kevin Day
quelle
können Sie näher darauf eingehen? Ich meine, inwieweit ist die Sperre von RandomAccess File besser oder sicherer als die von Stream One
Paralife
Link zu einem einfachen Beispiel unten gepostet
Touko
Paralife - Entschuldigung für die Verzögerung - hat gerade Ihre Frage bemerkt. Die Sperren von Streams sind Lesesperren (für Eingabestreams) und exklusive Vollkanal-Schreibsperren (für Ausgabestreams). Ich habe die Erfahrung gemacht, dass die Sperren von RAF eine feinkörnigere Steuerung ermöglichen (dh Sie können Teile einer Datei sperren).
Kevin Day
1

Unten finden Sie einen Beispiel-Snippet-Code zum Sperren einer Datei, bis der Prozess von JVM ausgeführt wird.

 public static void main(String[] args) throws InterruptedException {
    File file = new File(FILE_FULL_PATH_NAME);
    RandomAccessFile in = null;
    try {
        in = new RandomAccessFile(file, "rw");
        FileLock lock = in.getChannel().lock();
        try {

            while (in.read() != -1) {
                System.out.println(in.readLine());
            }
        } finally {
            lock.release();
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
Ajay Kumar
quelle