広告

Java : FileLock (ファイルロック) - API使用例

FileLock (Java SE 19 & JDK 19) の使用例まとめです。
だいたいのメソッドを網羅済みです。
API仕様のおともにどうぞ。


概要

ファイル領域上のロックを示すトークンです。

クラス構成

FileLock クラスを使うと、複数のプロセスから使うファイルをロック制御できます。
ファイルをロックするには FileChannel.lock メソッドを使います。

次のコード例では、2つのプロセスから lock.txt ファイルをロックします。
片方のプロセスがロック中に、別のプロセスから FileChannel.lock でロックしようとすると、ロックが解除されるまで待機します。

public class Child {
    public static void main(String[] args) throws IOException, InterruptedException {
        final var pid = ProcessHandle.current().pid();
        System.out.println("  child : start (pid=" + pid + ")");

        TimeUnit.SECONDS.sleep(1);

        final var file = Path.of("lock.txt");
        try (final var fc = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {

            System.out.println("  child : lock start (pid=" + pid + ")");
            try (final var lock = fc.lock()) {

                System.out.println("  child :   *** lock OK! *** (pid=" + pid + ")");
                System.out.println("  child :   sleep 5 seconds ... (pid=" + pid + ")");
                TimeUnit.SECONDS.sleep(5);
            }

            System.out.println("  child : lock end (pid=" + pid + ")");
        }

        System.out.println("  child : end (pid=" + pid + ")");
    }
}
public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        System.out.println("main : start");

        // プロセスを2つ起動します。
        final var builder = new ProcessBuilder("java", "Child").inheritIO();
        final var p1 = builder.start();
        final var p2 = builder.start();

        p1.waitFor();
        p2.waitFor();

        System.out.println("main : end");
    }
}

// 結果
// ↓
//> java Main
//main : start
//  child : start (pid=16164)
//  child : start (pid=11592)
//  child : lock start (pid=16164)
//  child :   *** lock OK! *** (pid=16164)
//  child :   sleep 5 seconds ... (pid=16164)
//  child : lock start (pid=11592)
//  child : lock end (pid=16164)
//  child :   *** lock OK! *** (pid=11592)
//  child : end (pid=16164)
//  child :   sleep 5 seconds ... (pid=11592)
//  child : lock end (pid=11592)
//  child : end (pid=11592)
//main : end

もしくは、FileChannel.tryLock メソッドも使えます。
lock メソッドとは違い、もしロックされているときは待機せずに null を返します。

public class Child {
    public static void main(String[] args) throws IOException, InterruptedException {
        final var pid = ProcessHandle.current().pid();
        System.out.println("  child : start (pid=" + pid + ")");

        TimeUnit.SECONDS.sleep(1);

        final var file = Path.of("lock.txt");
        try (final var fc = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {

            System.out.println("  child : lock start (pid=" + pid + ")");

            // ロックが取れるまで繰り返します。
            while (true) {
                try (final var lock = fc.tryLock()) {
                    if (lock != null) {
                        System.out.println("  child :   *** lock OK! *** (pid=" + pid + ")");
                        System.out.println("  child :   sleep 5 seconds ... (pid=" + pid + ")");
                        TimeUnit.SECONDS.sleep(5);
                        break;
                    } else {
                        System.out.println("  child :   *** lock NG! *** (pid=" + pid + ")");
                        System.out.println("  child :   sleep 1 second ... (pid=" + pid + ")");
                        TimeUnit.SECONDS.sleep(1);
                    }
                }
            }

            System.out.println("  child : lock end (pid=" + pid + ")");
        }

        System.out.println("  child : end (pid=" + pid + ")");
    }
}
public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        System.out.println("main : start");

        // プロセスを2つ起動します。
        final var builder = new ProcessBuilder("java", "Child").inheritIO();
        final var p1 = builder.start();
        final var p2 = builder.start();

        p1.waitFor();
        p2.waitFor();

        System.out.println("main : end");
    }
}

// 結果
// ↓
//> java Main
//main : start
//  child : start (pid=19076)
//  child : start (pid=9308)
//  child : lock start (pid=19076)
//  child :   *** lock OK! *** (pid=19076)
//  child :   sleep 5 seconds ... (pid=19076)
//  child : lock start (pid=9308)
//  child :   *** lock NG! *** (pid=9308)
//  child :   sleep 1 second ... (pid=9308)
//  child :   *** lock NG! *** (pid=9308)
//  child :   sleep 1 second ... (pid=9308)
//  child :   *** lock NG! *** (pid=9308)
//  child :   sleep 1 second ... (pid=9308)
//  child :   *** lock NG! *** (pid=9308)
//  child :   sleep 1 second ... (pid=9308)
//  child :   *** lock NG! *** (pid=9308)
//  child :   sleep 1 second ... (pid=9308)
//  child : lock end (pid=19076)
//  child : end (pid=19076)
//  child :   *** lock OK! *** (pid=9308)
//  child :   sleep 5 seconds ... (pid=9308)
//  child : lock end (pid=9308)
//  child : end (pid=9308)
//main : end

注意

  • FileLock の動作はプラットフォームに依存します。詳細は API仕様をご確認ください。
  • 本記事のコード例は Windows 10 で実行したものです。

コンストラクタ

FileLock (AsynchronousFileChannel channel, long position, long size, boolean shared)

このクラスの新しいインスタンスを初期化します。

protected です。
独自にサブクラスを作ることは少ないと思いますので、コード例は割愛します。

FileLock (FileChannel channel, long position, long size, boolean shared)

このクラスの新しいインスタンスを初期化します。

protected です。
独自にサブクラスを作ることは少ないと思いますので、コード例は割愛します。

メソッド

Channel acquiredBy ()

このロックが獲得されたファイルを持つチャネルを返します。

final var file = Path.of("R:", "java-work", "lock.txt");
try (final var fc = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
    try (final var lock = fc.lock()) {
        final var ret = lock.acquiredBy();
        System.out.println(fc == ret); // true
    }
}

final FileChannel channel ()

このロックが獲得されたファイルを持つファイル・チャネルを返します。

final var file = Path.of("R:", "java-work", "lock.txt");
try (final var fc = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
    try (final var lock = fc.lock()) {
        final var ret = lock.channel();
        System.out.println(fc == ret); // true
    }
}

final void close ()

このメソッドは、release()メソッドを呼び出します。

可能であれば try-with-resources文 を使うことをおすすめします。

final var file = Path.of("R:", "java-work", "lock.txt");
try (final var fc = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
    try (final var lock = fc.lock()) {
        System.out.println(lock.isValid()); // true
    }
}

try-with-resources文を使わない例です。

final var file = Path.of("R:", "java-work", "lock.txt");
try (final var fc = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
    final var lock = fc.lock();
    try {
        System.out.println(lock.isValid()); // true
    } finally {
        lock.close();
    }

    System.out.println(lock.isValid()); // false
}

final boolean isShared ()

このロックが共有ロックであるかどうかを判断します。

public class Child {
    public static void main(String[] args) throws IOException, InterruptedException {
        if (args.length != 2) {
            throw new IllegalArgumentException();
        }
        final var type = args[0];
        final var shared = "shared".equals(args[1]);

        println(type, "start");

        TimeUnit.SECONDS.sleep(1);

        final var file = Path.of("lock.txt");
        try (final var fc = FileChannel.open(file,
                StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ)) {

            println(type, "tryLock shared = " + shared);
            try (final var lock = fc.tryLock(0, Long.MAX_VALUE, shared)) {
                if (lock != null) {
                    println(type, "  *** lock OK! ***");

                    final var ret = lock.isShared();
                    println(type, "  isShared = " + ret);

                    TimeUnit.SECONDS.sleep(5);
                } else {
                    println(type, "  *** lock NG! ***");
                }
            }
        }
    }

    private static void println(String type, String text) {
        System.out.printf("  child %s : %s%n", type, text);
    }
}
public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        if (args.length != 2) {
            throw new IllegalArgumentException();
        }

        System.out.println("main : start");

        final var sharedA = args[0];
        final var sharedB = args[1];

        System.out.println("main : child A = " + sharedA);
        System.out.println("main : child B = " + sharedB);

        // プロセスを2つ起動します。
        final var p1 = new ProcessBuilder("java", "Child", "A", sharedA).inheritIO().start();
        final var p2 = new ProcessBuilder("java", "Child", "B", sharedB).inheritIO().start();

        p1.waitFor();
        p2.waitFor();

        System.out.println("main : end");
    }
}

// 結果
// ↓
//> java Main shared shared
//main : start
//main : child A = shared
//main : child B = shared
//  child A : start
//  child B : start
//  child A : tryLock shared = true
//  child A :   *** lock OK! ***
//  child A :   isShared = true
//  child B : tryLock shared = true
//  child B :   *** lock OK! ***
//  child B :   isShared = true
//main : end
//
//> java Main exclusive shared
//main : start
//main : child A = exclusive
//main : child B = shared
//  child A : start
//  child B : start
//  child A : tryLock shared = false
//  child A :   *** lock OK! ***
//  child A :   isShared = false
//  child B : tryLock shared = true
//  child B :   *** lock NG! ***
//main : end
//
//> java Main shared exclusive
//main : start
//main : child A = shared
//main : child B = exclusive
//  child A : start
//  child B : start
//  child A : tryLock shared = true
//  child A :   *** lock OK! ***
//  child A :   isShared = true
//  child B : tryLock shared = false
//  child B :   *** lock NG! ***
//main : end
//
//> java Main exclusive exclusive
//main : start
//main : child A = exclusive
//main : child B = exclusive
//  child A : start
//  child B : start
//  child A : tryLock shared = false
//  child A :   *** lock OK! ***
//  child A :   isShared = false
//  child B : tryLock shared = false
//  child B :   *** lock NG! ***
//main : end

abstract boolean isValid ()

このロックが有効であるかどうかを判断します。

このメソッドの使用例は、close() にまとめて記載しました。
そちらのAPI使用例をご参照ください。

final boolean overlaps (long position, long size)

このロックが指定されたロック範囲とオーバーラップしているかどうかを判断します。

final var file = Path.of("R:", "java-work", "lock.txt");
try (final var fc = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
    try (final var lock = fc.lock(100, 200, false)) {

        System.out.println(lock.position()); // 100
        System.out.println(lock.size()); // 200

        System.out.println(lock.overlaps(0, 99)); // false
        System.out.println(lock.overlaps(0, 100)); // false
        System.out.println(lock.overlaps(0, 101)); // true

        System.out.println(lock.overlaps(99, 1)); // false
        System.out.println(lock.overlaps(100, 1)); // true
        System.out.println(lock.overlaps(101, 1)); // true

        System.out.println(lock.overlaps(299, 1)); // true
        System.out.println(lock.overlaps(300, 1)); // false
        System.out.println(lock.overlaps(301, 1)); // false

        System.out.println(lock.overlaps(0, Long.MAX_VALUE)); // true
    }
}

final long position ()

ファイル内のロックされた領域の最初のバイトの位置を返します。

public class Child {
    public static void main(String[] args) throws IOException, InterruptedException {
        if (args.length != 3) {
            throw new IllegalArgumentException();
        }
        final var type = args[0];
        final var position = Long.parseLong(args[1]);
        final var size = Integer.parseInt(args[2]);

        println(type, "start");

        TimeUnit.SECONDS.sleep(1);

        final var file = Path.of("lock.txt");
        try (final var fc = FileChannel.open(file,
                StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ)) {

            println(type, "tryLock position = %d size = %d".formatted(position, size));
            try (final var lock = fc.tryLock(position, size, false)) {
                if (lock != null) {
                    println(type, "  *** lock OK! ***");
                    println(type, "  position = " + lock.position());
                    println(type, "  size = " + lock.size());

                    fc.position(position);
                    final var buff = ByteBuffer.allocate(size);
                    fc.read(buff);
                    println(type, "  read = " + Arrays.toString(buff.array()));

                    TimeUnit.SECONDS.sleep(5);
                } else {
                    println(type, "  *** lock NG! ***");
                }
            }
        }

        println(type, "end");
    }

    private static void println(String type, String text) {
        System.out.printf("  child %s : %s%n", type, text);
    }
}
public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        if (args.length != 4) {
            throw new IllegalArgumentException();
        }

        System.out.println("main : start");

        final var positionA = args[0];
        final var sizeA = args[1];

        final var positionB = args[2];
        final var sizeB = args[3];

        System.out.println("main : child A position = " + positionA + " size = " + sizeA);
        System.out.println("main : child B position = " + positionB + " size = " + sizeB);

        // 7バイトのデータを書き込みます。
        final var file = Path.of("lock.txt");
        final byte[] bytes = {10, 20, 30, 40, 50, 60, 70};
        Files.write(file, bytes);

        // プロセスを2つ起動します。
        final var p1 = new ProcessBuilder("java", "Child", "A", positionA, sizeA).inheritIO().start();
        final var p2 = new ProcessBuilder("java", "Child", "B", positionB, sizeB).inheritIO().start();

        p1.waitFor();
        p2.waitFor();

        System.out.println("main : end");
    }
}

// 結果
// ↓
//> java Main 0 7 0 7
//main : start
//main : child A position = 0 size = 7
//main : child B position = 0 size = 7
//  child A : start
//  child B : start
//  child A : tryLock position = 0 size = 7
//  child A :   *** lock OK! ***
//  child A :   position = 0
//  child A :   size = 7
//  child A :   read = [10, 20, 30, 40, 50, 60, 70]
//  child B : tryLock position = 0 size = 7
//  child B :   *** lock NG! ***
//  child B : end
//  child A : end
//main : end
//
//> java Main 0 3 3 4
//main : start
//main : child A position = 0 size = 3
//main : child B position = 3 size = 4
//  child A : start
//  child B : start
//  child A : tryLock position = 0 size = 3
//  child A :   *** lock OK! ***
//  child A :   position = 0
//  child A :   size = 3
//  child A :   read = [10, 20, 30]
//  child B : tryLock position = 3 size = 4
//  child B :   *** lock OK! ***
//  child B :   position = 3
//  child B :   size = 4
//  child B :   read = [40, 50, 60, 70]
//  child A : end
//  child B : end
//main : end

abstract void release ()

このロックを解除します。

このメソッドは close() と同等です。
release の代わりに、可能であれば try-with-resources文 を使うことをおすすめします。

final var file = Path.of("R:", "java-work", "lock.txt");
try (final var fc = FileChannel.open(file, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
    final var lock = fc.lock();
    try {
        System.out.println(lock.isValid()); // true
    } finally {
        lock.release();
    }

    System.out.println(lock.isValid()); // false
}

final long size ()

ロックされた領域のサイズをバイトで返します。

このメソッドの使用例は、position() にまとめて記載しました。
そちらのAPI使用例をご参照ください。

final String toString ()

このロックの範囲、種類、有効性を説明する文字列を返します。

final var file = Path.of("R:", "java-work", "lock.txt");
try (final var fc = FileChannel.open(file,
        StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ)) {

    try (final var lock = fc.lock()) {
        final var str = lock.toString();
        System.out.println(str); // sun.nio.ch.FileLockImpl[0:9223372036854775807 exclusive valid]
    }

    try (final var lock = fc.lock(100, 200, true)) {
        final var str = lock.toString();
        System.out.println(str); // sun.nio.ch.FileLockImpl[100:200 shared valid]
    }
}

関連記事

ページの先頭へ