Как заблокировать файл с помощью java (если возможно)


121

У меня есть процесс Java, который открывает файл с помощью FileReader. Как я могу предотвратить открытие этого файла другим (Java) процессом или, по крайней мере, уведомить этот второй процесс о том, что файл уже открыт? Это автоматически приводит к тому, что второй процесс получает исключение, если файл открыт (что решает мою проблему), или мне нужно явно открывать его в первом процессе с каким-то флагом или аргументом?

Чтобы уточнить:

У меня есть приложение Java, которое перечисляет папку и открывает каждый файл в списке для его обработки. Он обрабатывает каждый файл за другим. Обработка каждого файла состоит из его чтения и выполнения некоторых вычислений на основе содержимого и занимает около 2 минут. У меня также есть другое приложение Java, которое делает то же самое, но вместо этого записывает в файл. Я хочу иметь возможность запускать эти приложения одновременно, чтобы сценарий выглядел следующим образом. ReadApp перечисляет папку и находит файлы A, B, C. Он открывает файл A и начинает чтение. WriteApp выводит список папок и находит файлы A, B, C. Он открывает файл A, видит, что он открыт (по исключению или другим способом), и переходит к файлу B. ReadApp завершает файл A и переходит к B. Он видит, что он открыт и продолжает C. Очень важно, чтобы WriteApp не t писать, пока ReadApp читает тот же файл, или наоборот. Это разные процессы.


12
Вы имеете в виду «процесс» как в процессе (две JVM) или потоке (та же JVM). Влияние на ответ имеет первостепенное значение.
Стю Томпсон,

проверьте пример кода, показывающий решение здесь: stackoverflow.com/a/58871479/5154619
Davi Cavalcanti

Ответы:


117

FileChannel.lock, вероятно, то, что вам нужно.

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

(Отказ от ответственности: код не компилируется и, конечно же, не тестируется.)

Обратите внимание на раздел, озаглавленный «Зависимости платформ» в документации API для FileLock .


22
Что еще более важно, поймите, что блокировка для JVM не подходит для блокировки файла для доступа отдельных потоков в пределах одной JVM.
Стю Томпсон,

11
Вам нужен записываемый поток (т.е. FileOutputStream).
Хавьер

@ Хавьер А ты? Я не пробовал. Из документации API ничего не выходит, что это требование. FileOutputStreamне будет много пользы для Reader.
Том Хотин - tackline

18
Да, я пробовал, и он выдает NonWritableChannelException, потому что lock()пытается получить эксклюзивную блокировку, но для этого требуется доступ на запись. Если у вас есть входной поток, вы можете использовать его, lock(0L, Long.MAX_VALUE, false)который получает общую блокировку и требует только доступа для чтения. Вы также можете использовать RandomAccessFileоткрытый в режиме чтения-записи, если вы хотите исключительную блокировку при чтении ... но это запретит одновременное чтение.
Хавьер

6
@ Хавьер Я думаю, вы хотите сказать lock(0L, Long.MAX_VALUE, true), что нет lock(0L, Long.MAX_VALUE, false). последний аргумент там boolean shared docs.oracle.com/javase/8/docs/api/java/nio/channels/…
Джон

60

Не используйте классы в java.ioпакете, вместо этого используйте java.nioпакет. Последний имеет FileLockкласс. Вы можете применить блокировку к файлу 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) {
    }

кстати, я записываю в файл блокировки текущий pid из этого совета stackoverflow.com/a/35885/1422630 , поэтому после того, как я смогу прочитать его в новом экземпляре!
Сила Водолея

1
этот выглядел хорошо, но не работает. Я получаю исключение OverlappingFileLockException каждый раз, даже когда файл даже не существовал
Гавриил

1
Проблема произойдет, если вы вызовете tryLock после блокировки, как написано в примере
Игорь Вукович

17

Если вы можете использовать Java NIO ( JDK 1.4 или выше ), я думаю, вы ищетеjava.nio.channels.FileChannel.lock()

FileChannel.lock ()


5
Может быть. Зависит от того, что OP имел в виду под словом «процесс». «Блокировки файлов устанавливаются от имени всей виртуальной машины Java. Они не подходят для управления доступом к файлу несколькими потоками внутри одной виртуальной машины».
Стю Томпсон,

@Stu: Я знаю, что вы давно ответили на этот вопрос, но я надеюсь, что вы сможете уточнить, что вы имели в виду, когда сказалиFile 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
Тханг Фам

3
@Harry Он цитирует документы: download.oracle.com/javase/6/docs/api/java/nio/channels/… Это означает, что он невидим для потоков, но влияет на другие процессы.
Artur Czajka

@Harry: Чтобы добавить еще больше к этим некро-комментариям, представьте, что вы используете Java для обслуживания веб-сайтов с помощью Tomcat. У вас может быть много потоков, каждый из которых обслуживает один запрос из веб-браузера. Однако все они управляют одним и тем же механизмом блокировки файлов, как и многие повара на кухне. Один запрос может завершиться в середине второго, и внезапно ваш файл «разблокируется», пока вы все еще были в середине чего-то, а затем какой-то другой процесс, такой как cronjob, может заблокировать его, и тогда вы неожиданно потеряли свой блокировка, и ваш запрос не может быть завершен ...
Дариен


5

Возможно, это не то, что вы ищете, но чтобы взглянуть на проблему под другим углом ...

Могут ли эти два процесса Java получить доступ к одному и тому же файлу в одном приложении? Возможно, вы можете просто отфильтровать весь доступ к файлу с помощью единственного синхронизированного метода (или, что еще лучше, с помощью JSR-166 )? Таким образом, вы можете контролировать доступ к файлу и, возможно, даже ставить запросы доступа в очередь.


3
Два процесса не могут использовать синхронизацию, только два потока в одном процессе.
Marquis of Lorne

3

Используйте RandomAccessFile, получите его канал, затем вызовите lock (). Канал, предоставляемый потоками ввода или вывода, не имеет достаточных прав для правильной блокировки. Обязательно вызовите unlock () в блоке finally (закрытие файла не обязательно снимает блокировку).


можешь уточнить? Я имею в виду, насколько блокировка с помощью RandomAccess File лучше или безопаснее, чем с помощью потоковой передачи
Paralife

Ссылка на простой пример, размещенный ниже
Touko

Paralife - извините за задержку - только что заметил ваш вопрос. Блокировки потоков будут блокировками чтения (для входных потоков) и эксклюзивными блокировками записи всего канала (для выходных потоков). По моему опыту, блокировки из RAF позволяют более детально управлять (то есть вы можете блокировать части файла).
Кевин Дэй,

1

Ниже приведен пример кода фрагмента для блокировки файла до тех пор, пока он не будет обработан 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();
        }
    }

}

0

Используйте это для unix, если вы переносите с помощью winscp или ftp:

public static void isFileReady(File entry) throws Exception {
        long realFileSize = entry.length();
        long currentFileSize = 0;
        do {
            try (FileInputStream fis = new FileInputStream(entry);) {
                currentFileSize = 0;
                while (fis.available() > 0) {
                    byte[] b = new byte[1024];
                    int nResult = fis.read(b);
                    currentFileSize += nResult;
                    if (nResult == -1)
                        break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("currentFileSize=" + currentFileSize + ", realFileSize=" + realFileSize);
        } while (currentFileSize != realFileSize);
    }
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.