web-dev-qa-db-ja.com

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番目のテストを実行するには、クラスパスにApachecommons-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つは失敗し、最後の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を誤解しているか、問題の適切な解決策ではありません。

13
Dónal

別のプロセスに関するメッセージは、システム上の一部のプロセスでファイルが開いていることを意味します。そのプロセスが、ファイルの削除/名前変更を試みているプロセスと同じであるかどうかは実際にはチェックされません。この場合、同じプログラムでファイルが開かれます。ロックを取得するために開きました。ここでのロックは、特に削除または名前変更操作のためにこれを行っている場合、ほとんどまたはまったく価値がありません。

やりたいことをするためには、ディレクトリエントリをロックする必要があります。これはJavaでは使用できず、Windowsでは使用できない場合があります。これらの(削除および挿入)操作はアトミックです。つまり、オペレーティングシステムがディレクトリおよびその他のファイルシステム構造のロックを処理します。別のプロセス(または自分の)がファイルを開いている場合、これらの操作は失敗します。ファイルを排他的にロックしようとしていて(ディレクトリエントリ)、別のプロセス(または自分の)がファイルを開いている場合、ロック違いはありませんが、ロックを実行しようとすると複雑になり、この場合、操作が不可能になります(つまり、操作を実行する前にファイルが常に開かれます)。

これで、ファイルへの書き込みは有効なロック操作になります。書き込みたいファイルまたはファイルの一部をロックすると、機能します。 Windowsでは、このロックメカニズムは必須であるため、別のオープン/ファイル記述子は、ロックされている部分に書き込むことができません。

[〜#〜]編集[〜#〜]

_FileChannel.lock_のJavaDocによると、これはFileChannel.lock(0L, Long.MAXVALUE, false)を呼び出すのと同じです。これは、最初のバイトから最後のバイトまでの領域に対する排他ロックです。

第二に、FileLockのJavaDocによると

ロックが実際に別のプログラムがロックされた領域のコンテンツにアクセスするのを妨げるかどうかはシステムに依存するため、指定されていません。一部のシステムのネイティブファイルロック機能は単なる助言です。つまり、プログラムは、データの整合性を保証するために、既知のロックプロトコルを協調的に監視する必要があります。他のシステムでは、ネイティブファイルロックが必須です。つまり、1つのプログラムがファイルの領域をロックすると、他のプログラムがロックに違反する方法でその領域にアクセスできなくなります。さらに他のシステムでは、ネイティブファイルロックがアドバイザリであるか必須であるかは、ファイルごとに構成できます。プラットフォーム間で一貫性のある正しい動作を保証するために、このAPIによって提供されるロックは、アドバイザリロックであるかのように使用することを強くお勧めします

[〜#〜]編集[〜#〜]

testWriteメソッドの場合。コモンズI/O静的メソッドのJavaDocはまばらですが、「ファイルが存在しない場合はファイルを作成するファイルに文字列を書き込みます。」と表示され、このメソッドとして存在すると、開かれたストリームの代わりにFileが必要になります。 、ファイルを内部的に開く可能性があります。おそらく、共有アクセスでファイルを開いておらず、追加アクセスのために開いていません。これは、既存のオープンとロック(ロックを取得するチャネルを取得するためのオープン)がその使用をブロックしていることを意味します。さらに理解するには、そのメソッドのソースを取得して、それが何をしているのかを調べる必要があります。

[〜#〜]編集[〜#〜]

申し訳ありませんが、私は正直に立っています。 Windows APIを確認しましたが、Windowsではファイルロックが必須です。これが書き込みが失敗する理由です。最初に開いて(_new RandomAccessFile_)ロックすると、ファイルがロックされます。文字列を書き込むためのオープンは成功しますが、別のオープン(ファイル記述子)が必須の排他ロックの下でファイルの全範囲を持っているため、書き込みは失敗します。つまり、ロックが解放されるまで、他のファイル記述子はファイルに書き込むことができません。

ロックはファイル記述子に関連付けられていることに注意してください[〜#〜] not [〜#〜]プロセスまたはスレッド。

16
Kevin Brock

目的のロックは ファイル内の領域のロック ですが、ファイル自体はロックされていないため、領域がロックされている間は、ファイルを削除したり名前を変更したりすることはできません。

Commons Transaction プロジェクトを確認することをお勧めします。

1
Eugene Kuleshov

deleteおよびrename操作はオペレーティングシステムによって実行され、(ほとんどのオペレーティングシステムで)アトミックであるため、ロックは必要ありません。

文字列をファイルに書き込むには、最初に一時ファイル(foo.tmpなど)に書き込み、準備ができたら名前を変更する方が簡単です。

1
dogbane

名前の変更、削除、...などのアクションを実行する前に、メソッドrelease()を使用してファイルを解放する必要があります。

0
itro

Javaファイルロックは、他のロックから保護するためにのみ指定されており、他には何もありません。特定のプラットフォームでの動作、つまり追加のセマンティクスは、プラットフォーム固有です。

0
user207421