Как работает FileLock?

Я пытаюсь использовать FileLock для получения эксклюзивного доступа к файлу, чтобы:

  • удалить его
  • переименовать его
  • напишите ему

Так как в Windows (по крайней мере) кажется, что вы не можете удалить, переименовать или записать файл, который уже используется. Код, который я написал, выглядит примерно так:

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;

public abstract class LockedFileOperation {

    public void execute(File file) throws IOException {

        if (!file.exists()) {
            throw new FileNotFoundException(file.getAbsolutePath());
        }

        FileChannel channel = new RandomAccessFile(file, "rw").getChannel();

        try {
            // Get an exclusive lock on the whole file
            FileLock lock = channel.lock();

            try {
                doWithLockedFile(file);
            } finally {
                lock.release();
            }
        } finally {
            channel.close();
        }
    }

    public abstract void doWithLockedFile(File file) throws IOException;
}

Вот некоторые модульные тесты, которые демонстрируют проблему. Для запуска 3-го теста вам нужно будет использовать Apache commons-io в своем классе.

import java.io.File;
import java.io.IOException;

import junit.framework.TestCase;

public class LockedFileOperationTest extends TestCase {

    private File testFile;

    @Override
    protected void setUp() throws Exception {

        String tmpDir = System.getProperty("java.io.tmpdir");
        testFile = new File(tmpDir, "test.tmp");

        if (!testFile.exists() && !testFile.createNewFile()) {
            throw new IOException("Failed to create test file: " + testFile);
        }
    }

    public void testRename() throws IOException {
        new LockedFileOperation() {

            @Override
            public void doWithLockedFile(File file) throws IOException {
                if (!file.renameTo(new File("C:/Temp/foo"))) {
                    fail();
                }
            }
        }.execute(testFile);
    }

    public void testDelete() throws IOException {
        new LockedFileOperation() {

            @Override
            public void doWithLockedFile(File file) throws IOException {
                if (!file.delete()) {
                    fail();
                }
            }
        }.execute(testFile);
    }

    public void testWrite() throws IOException {
        new LockedFileOperation() {

            @Override
            public void doWithLockedFile(File file) throws IOException {
                org.apache.commons.io.FileUtils.writeStringToFile(file, "file content");
            }
        }.execute(testFile);
    }
}

Ни один из тестов не проходит. Первые 2 завершаются с ошибкой, а последнее исключает это исключение:

java.io.IOException: The process cannot access the file because another process has locked a portion of the file
    at java.io.FileOutputStream.writeBytes(Native Method)
    at java.io.FileOutputStream.write(FileOutputStream.java:247)
    at org.apache.commons.io.IOUtils.write(IOUtils.java:784)
    at org.apache.commons.io.IOUtils.write(IOUtils.java:808)
    at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:1251)
    at org.apache.commons.io.FileUtils.writeStringToFile(FileUtils.java:1265)

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

Либо я недопонимаю FileLock, либо это не подходящее решение для моей проблемы.

Ответы

Ответ 1

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

Чтобы сделать то, что вы хотите, вам нужно будет заблокировать запись в каталоге. Это недоступно на Java и может быть недоступно в Windows. Эти операции (удаление и вставка) являются атомарными. Это означает, что операционная система заботится о блокировке каталога и других структур файловой системы для вас. Если другой процесс (или ваш собственный) откроет файл, эти операции потерпят неудачу. Если вы пытаетесь заблокировать файл исключительно (запись в каталоге), а другой процесс (или ваш собственный) откроет файл, блокировка завершится неудачно. Нет никакой разницы, но попытка блокировки просто усложняется и в этом случае делает невозможной операцию (то есть файлы всегда открываются, прежде чем пытаться выполнить операцию).

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

ИЗМЕНИТЬ

В соответствии с JavaDoc на FileChannel.lock он совпадает с вызовом FileChannel.lock(0L, Long.MAXVALUE, false). Это исключительная блокировка в области от первого байта до последнего.

Во-вторых, согласно JavaDoc на FileLock

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

ИЗМЕНИТЬ

Для метода testWrite. JavaDoc в статическом методе ввода-вывода общего доступа разрежен, но говорит: "Записывает строку в файл, создающий файл, если он не существует..", и поскольку этот метод принимает File вместо открытого потока, вероятно, открывает файл внутри. Вероятно, он не открывает файл с общим доступом, а также открывает доступ для добавления доступа. Это означает, что существующие блокировки и блокировки (открытые для получения канала, из которого можно получить блокировку) блокируют использование. Чтобы понять еще больше, вам нужно будет получить источник этого метода и посмотреть, что он делает.

ИЗМЕНИТЬ

Извините, я стою исправлено. Я проверил Windows API, и блокировка файлов обязательна для Windows. Вот почему запись не удалась. Первый открытый (ваш new RandomAccessFile) и блокировка блокирует файл. Открытая для записи строка преуспевает, но запись терпит неудачу, потому что другой открытый (файловый дескриптор) имеет полную длину файла с обязательной исключительной блокировкой, то есть никакой другой файловый дескриптор не может записывать файл до тех пор, пока блокировка не будет выпущена.

Обратите внимание, что блокировка связана с файловым дескриптором НЕ процессом или потоком.

Ответ 3

Операции delete и rename выполняются операционной системой и являются атомарными (в большинстве операционных систем), поэтому блокировка не требуется.

Чтобы записать строку в файл, было бы проще сначала записать во временный файл (например, foo.tmp), а затем переименовать его, когда он будет готов.

Ответ 4

Блокировки файлов Java указываются только для защиты от других блокировок, и ничего больше. Как они ведут себя на определенных платформах, то есть любая дополнительная семантика, зависит от платформы.

Ответ 5

Вы должны выпустить файл с помощью метода release() перед выполнением любых действий, таких как переименование или удаление или....