Как работает FileLock? - PullRequest
       6

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

13 голосов
/ 26 октября 2010

Я пытался использовать 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, либо это не подходящее решение для моей проблемы.

Ответы [ 5 ]

16 голосов
/ 27 октября 2010

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

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

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

EDIT

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

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

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

EDIT

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

EDIT

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

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

1 голос
/ 26 октября 2010

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

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

1 голос
/ 26 октября 2010

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

Возможно, вы захотите взглянуть на проект Commons Transaction .

0 голосов
/ 03 июля 2012

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

0 голосов
/ 27 октября 2010

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

...