Jak mogę zablokować plik za pomocą Java (jeśli to możliwe)

121

Mam proces Java, który otwiera plik za pomocą FileReader. Jak mogę zapobiec otwarciu tego pliku przez inny proces (Java) lub przynajmniej powiadomić ten drugi proces, że plik jest już otwarty? Czy to automatycznie powoduje, że drugi proces otrzymuje wyjątek, jeśli plik jest otwarty (co rozwiązuje mój problem), czy też muszę jawnie otworzyć go w pierwszym procesie z jakąś flagą lub argumentem?

W celu wyjaśnienia:

Mam aplikację Java, która wyświetla folder i otwiera każdy plik na liście w celu jego przetworzenia. Przetwarza każdy plik po drugim. Przetwarzanie każdego pliku polega na przeczytaniu go i wykonaniu pewnych obliczeń na podstawie zawartości i trwa około 2 minut. Mam też inną aplikację Java, która robi to samo, ale zamiast tego zapisuje w pliku. Chcę mieć możliwość uruchamiania tych aplikacji w tym samym czasie, więc scenariusz wygląda tak. ReadApp wyświetla listę folderów i znajduje pliki A, B, C. Otwiera plik A i rozpoczyna odczytywanie. WriteApp wyświetla listę folderów i znajduje pliki A, B, C. Otwiera plik A, widzi, że jest otwarty (przez wyjątek lub w jakikolwiek inny sposób) i przechodzi do pliku B. ReadApp kończy plik A i kontynuuje do B. jest otwarta i kontynuuje C. Ważne jest, aby WriteApp nie t pisz, gdy ReadApp czyta ten sam plik lub odwrotnie. To są różne procesy.

Paralife
źródło
12
Czy masz na myśli „proces” jako proces (dwie maszyny JVM) czy wątek (ta sama maszyna JVM). Najważniejszy jest wpływ na odpowiedź.
Stu Thompson,
sprawdź przykładowy kod przedstawiający rozwiązanie tutaj: stackoverflow.com/a/58871479/5154619
Davi Cavalcanti

Odpowiedzi:

117

FileChannel.lock jest prawdopodobnie tym, czego chcesz.

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

(Zastrzeżenie: kod nie został skompilowany i na pewno nie został przetestowany).

Zwróć uwagę na sekcję zatytułowaną „Zależności platform” w dokumencie API dla FileLock .

Tom Hawtin - haczyk
źródło
22
Co ważniejsze, pamiętaj, że blokada dla maszyny JVM i nie nadaje się do blokowania dostępu do pliku dla poszczególnych wątków w ramach jednej maszyny JVM.
Stu Thompson,
11
Potrzebujesz zapisywalnego strumienia (tj FileOutputStream.).
Javier
@Javier Czy ty? Nie próbowałem. Nic nie wyskakuje z dokumentacji API, mówiąc, że jest to wymagane. FileOutputStreamnie będzie zbyt przydatny dla Reader.
Tom Hawtin - tackline
18
Tak, próbowałem i rzuca się NonWritableChannelException, ponieważ lock()próbuje uzyskać wyłączną blokadę, ale to wymaga dostępu do zapisu. Jeśli masz strumień wejściowy , możesz użyć, lock(0L, Long.MAX_VALUE, false)który uzyskuje wspólną blokadę i wymaga tylko dostępu do odczytu. Możesz także użyć RandomAccessFileotwartego w trybie do odczytu i zapisu, jeśli chcesz mieć wyłączną blokadę podczas czytania ... ale to zabroniłoby jednoczesnych czytelników.
Javier
6
@Javier Myślę, że chcesz powiedzieć lock(0L, Long.MAX_VALUE, true), nie lock(0L, Long.MAX_VALUE, false). ostatni argument jest boolean shared docs.oracle.com/javase/8/docs/api/java/nio/channels/…
john sullivan
60

Nie używaj klas w java.iopakiecie, zamiast tego użyj java.niopakietu. Ten ostatni ma FileLockklasę. Możesz zastosować blokadę do pliku 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
źródło
btw, piszę w pliku blokady aktualny pid z tej wskazówki stackoverflow.com/a/35885/1422630 , więc po przeczytaniu go na nowej instancji!
Aquarius Power
1
ten wyglądał dobrze, ale nie działa. Otrzymuję OverlappingFileLockException za każdym razem, nawet gdy plik nie nawet postać istnieje
Gavriel
1
Problem się stanie, jeśli wywołasz tryLock po locku, jak jest napisane w przykładzie
Igor Vuković
17

Jeśli możesz używać Java NIO ( JDK 1.4 lub nowszej ), to myślę, że szukaszjava.nio.channels.FileChannel.lock()

FileChannel.lock ()

KC Baltz
źródło
5
Może. Zależy od tego, co PO oznacza „proces”. „Blokady plików są utrzymywane w imieniu całej wirtualnej maszyny Java. Nie nadają się do kontrolowania dostępu do pliku przez wiele wątków w ramach tej samej maszyny wirtualnej”.
Stu Thompson,
@Stu: Wiem, że odpowiedziałeś na to pytanie dawno temu, ale mam nadzieję, że możesz wyjaśnić, co masz na myśli, mówiącFile 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 Cytuje z dokumentacji: download.oracle.com/javase/6/docs/api/java/nio/channels/ ... Oznacza to, że jest niewidoczny dla wątków, ale wpływa na inne procesy.
Artur Czajka
@Harry: Aby dodać jeszcze więcej do tych nekro-komentarzy, wyobraź sobie, że używasz Javy do obsługi stron internetowych za pomocą Tomcat. Możesz mieć wiele wątków, z których każdy obsługuje jedno żądanie z przeglądarki internetowej. Jednak wszyscy kontrolują ten sam mechanizm blokowania plików, co zbyt wielu kucharzy w kuchni. Jedno żądanie może zakończyć się w połowie drugiego i nagle twój plik został "odblokowany", gdy byłeś jeszcze w środku czegoś, a potem jakiś inny proces, taki jak cronjob, może go zablokować, a wtedy nieoczekiwanie straciłeś zamek i twoja prośba nie może zakończyć się ...
Darien
5

Może to nie jest to, czego szukasz, ale w interesie podejścia do problemu z innej perspektywy ....

Czy te dwa procesy Java mogą chcieć uzyskać dostęp do tego samego pliku w tej samej aplikacji? Może uda się po prostu przefiltrować cały dostęp do pliku jedną, zsynchronizowaną metodą (lub jeszcze lepiej, używając JSR-166 )? W ten sposób możesz kontrolować dostęp do pliku, a być może nawet ustawiać żądania dostępu do kolejki.

pkaeding
źródło
3
Dwa procesy nie mogą korzystać z synchronizacji, tylko dwa wątki w tym samym procesie.
Markiz Lorne
3

Użyj RandomAccessFile, pobierz jego kanał, a następnie wywołaj lock (). Kanał dostarczany przez strumienie wejściowe lub wyjściowe nie ma wystarczających uprawnień, aby prawidłowo blokować. Pamiętaj, aby wywołać funkcję unlock () w ostatnim bloku (zamknięcie pliku niekoniecznie zwalnia blokadę).

Kevin Day
źródło
czy możesz rozwinąć? Mam na myśli, w jakim stopniu blokada przez RandomAccess File jest lepsza lub bezpieczniejsza niż strumieni jeden
Paralife
Link do prostego przykładu zamieszczonego poniżej
Touko,
Paralife - przepraszam za opóźnienie - właśnie zauważyłem twoje pytanie. Blokady ze strumieni będą blokadami odczytu (dla strumieni wejściowych) i wyłącznymi, pełnymi blokadami zapisu kanału (dla strumieni wyjściowych). Z mojego doświadczenia wynika, że ​​blokady firmy RAF pozwalają na bardziej precyzyjną kontrolę (tj. Można blokować fragmenty pliku).
Kevin Day
1

Poniżej znajduje się przykładowy fragment kodu służący do blokowania pliku do czasu zakończenia jego przetwarzania przez JVM.

 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
źródło