Рубрики
Без рубрики

Как заблокировать файл в Java

Узнайте о различных методах блокировки файлов с помощью библиотеки Java NIO.

Автор оригинала: baeldung.

1. Обзор

При чтении или записи файлов мы должны убедиться, что установлены надлежащие механизмы блокировки файлов. Это обеспечивает целостность данных в параллельных приложениях ввода-вывода.

В этом уроке мы рассмотрим различные подходы к достижению этой цели с помощью библиотеки Java NIO //.

2. Введение в блокировку файлов

В общем, существует два типа замков :

    • Эксклюзивные блокировки — также известные как блокировки записи
    • Общие блокировки — также называемые блокировками чтения

Проще говоря, эксклюзивная блокировка предотвращает все другие операции, включая чтение, во время завершения операции записи.

В отличие от этого, общая блокировка позволяет одновременно читать более одного процесса. Смысл блокировки чтения заключается в том, чтобы предотвратить получение блокировки записи другим процессом. Как правило, файл в согласованном состоянии действительно должен быть доступен для чтения любым процессом.

В следующем разделе мы рассмотрим, как Java обрабатывает эти типы блокировок.

3. Блокировка файлов в Java

Библиотека Java NIO позволяет блокировать файлы на уровне операционной системы. Для этой цели используются методы lock() и tryLock() файла|/.

Мы можем создать Файловый канал через FileInputStream , FileOutputStream или RandomAccessFile . Все три имеют метод getChannel() , который возвращает FileChannel .

В качестве альтернативы мы можем создать файловый канал непосредственно с помощью статического открытого метода:

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

Далее мы рассмотрим различные варианты получения эксклюзивных и общих блокировок в Java. Чтобы узнать больше о файловых каналах, ознакомьтесь с нашим руководством по Java FileChannel tutorial.

4. Эксклюзивные Замки

Как мы уже узнали, при записи в файл мы можем запретить другим процессам читать или записывать в него, используя эксклюзивную блокировку .

Мы получаем эксклюзивные блокировки, вызывая lock() или tryLock() в классе FileChannel|/. Мы также можем использовать их перегруженные методы:

  • блокировка(длинная позиция, длинный размер, общий логический)
  • tryLock(длинная позиция, длинный размер, логический общий)

В этих случаях параметр shared должен быть установлен в false .

Чтобы получить эксклюзивную блокировку, мы должны использовать доступный для записи файловый канал . Мы можем создать его с помощью getChannel() методов FileOutputStream или RandomAccessFile . В качестве альтернативы, как упоминалось ранее, мы можем использовать статический метод open класса FileChannel . Все, что нам нужно, это установить второй аргумент в StandardOpenOption.ДОБАВИТЬ :

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

4.1. Эксклюзивные блокировки с использованием потока вывода файлов

Файловый канал , созданный из потока |/FileOutputStream , доступен для записи. Таким образом, мы можем приобрести эксклюзивный замок:

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

Здесь channel.lock() будет либо блокировать, пока не получит блокировку, либо выдаст исключение. Например, если указанный регион уже заблокирован, возникает исключение OverlappingFileLockException . Полный список возможных исключений см. в файле Javadoc .

Мы также можем выполнить неблокирующую блокировку с помощью channel.tryLock() . Если ему не удается получить блокировку из-за того, что другая программа содержит перекрывающуюся, он возвращает null . Если это не удается сделать по какой-либо другой причине, то создается соответствующее исключение.

4.2. Эксклюзивные блокировки с использованием файла RandomAccessFile

С помощью файла RandomAccessFile нам нужно установить флаги для второго параметра конструктора /.

Здесь мы собираемся открыть файл с правами на чтение и запись:

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

Если мы откроем файл в режиме только для чтения и попытаемся записать его в канал, он вызовет исключение NonWritableChannelException .

4.3. Эксклюзивные блокировки Требуют доступного для записи файлового канала

Как уже упоминалось ранее, эксклюзивные блокировки требуют доступного для записи канала. Поэтому мы не можем получить эксклюзивную блокировку через FileChannel , созданный из FileInputStream :

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
}

В приведенном выше примере метод lock() вызовет исключение NonWritableChannelException . Действительно, это происходит потому, что мы вызываем getChannel на FileInputStream , который создает канал только для чтения.

Этот пример просто демонстрирует, что мы не можем писать в канал, недоступный для записи. В реальном сценарии мы бы не поймали и не переосмыслили исключение.

5. Общие Блокировки

Помните, что общие блокировки также называются read locks. Следовательно, чтобы получить блокировку чтения, мы должны использовать читаемый файловый канал .

Такой FileChannel можно получить, вызвав метод getChannel() на FileInputStream или RandomAccessFile . Опять же, другой вариант-использовать статический метод open класса FileChannel|/. В этом случае мы устанавливаем второй аргумент в StandardOpenOption.ЧИТАТЬ :

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

Здесь следует отметить, что мы решили заблокировать весь файл, вызвав lock(0, Long.MAX_VALUE, true) . Мы также могли бы заблокировать только определенную область файла, изменив первые два параметра на разные значения. Третий параметр должен быть установлен в true в случае общей блокировки.

Чтобы все было просто, мы будем блокировать весь файл во всех приведенных ниже примерах, но имейте в виду, что мы всегда можем заблокировать определенную область файла.

5.1. Общие блокировки с использованием FileInputStream

FileChannel , полученный из FileInputStream , читаем. Таким образом, мы можем получить общую блокировку:

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
}

В приведенном выше фрагменте вызов lock() на канале будет успешным. Это связано с тем, что для общей блокировки требуется только, чтобы канал был читаемым. Это так, поскольку мы создали его из FileInputStream .

5.2. Общие блокировки с использованием файла RandomAccessFile

На этот раз мы можем открыть файл только с разрешениями read :

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
}

В этом примере мы создали файл RandomAccessFile с правами на чтение. Мы можем создать из него читаемый канал и, таким образом, создать общую блокировку.

5.3. Для общих блокировок Требуется Читаемый файловый канал

По этой причине мы не можем получить общую блокировку через FileChannel , созданный из 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
}

В этом примере вызов lock() пытается получить общую блокировку канала, созданного из FileOutputStream . Такой канал доступен только для записи. Это не удовлетворяет потребности в том, чтобы канал был читаемым. Это вызовет исключение NonWritableChannelException .

Опять же, этот фрагмент просто демонстрирует, что мы не можем читать с нечитаемого канала.

6. Что следует учитывать

На практике использование блокировок файлов затруднено; механизмы блокировки не являются портативными. Нам нужно будет разработать нашу логику блокировки с учетом этого.

В системах POSIX блокировки носят рекомендательный характер. Различные процессы чтения или записи в данный файл должны согласовать протокол блокировки. Это обеспечит целостность файла. Сама ОС не будет применять никаких блокировок.

В Windows блокировки будут исключительными, если общий доступ не разрешен. Обсуждение преимуществ или недостатков механизмов, специфичных для ОС, выходит за рамки данной статьи. Тем не менее, важно знать эти нюансы при реализации механизма блокировки.

7. Заключение

В этом уроке мы рассмотрели несколько различных вариантов получения блокировок файлов в Java.

Во-первых, мы начали с понимания двух основных механизмов блокировки и того, как библиотека Java NIO облегчает блокировку файлов. Затем мы рассмотрели ряд простых примеров, показывающих, что мы можем получить эксклюзивные и общие блокировки в наших приложениях. Мы также рассмотрели типы типичных исключений, с которыми мы можем столкнуться при работе с блокировками файлов.

Как всегда, исходный код примеров доступен на GitHub .