広告

Java : Lock (同期) - API使用例

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


概要

Lock実装は、synchronizedのメソッドや文を使用することで取得可能なロック操作よりも広範なロック操作を提供します。 この実装を使用すると、より柔軟な構築を行ったり、まったく異なるプロパティを保持したり、関連する複数のConditionオブジェクトをサポートしたりできるようになります。

クラス構成

Lock インタフェースを使うと、synchronized 文に相当する同期処理を、より柔軟に構築することができます。
主な実装には ReentrantLock クラスがあります。

例えば、synchronized 文は、同期の開始と終了はブロック単位でしか行うことができません。

class Sample {
    private final Object obj = new Object();

    void func() {
        synchronized (obj) { // 同期開始
            ...
        } // 同期終了
    }
}

Lock を使うと、ブロック単位でなくても同期を開始、終了することができます。

class Sample {
    private final Lock lock = new ReentrantLock();

    void funcA() {
        lock.lock(); // 同期開始
        ...
    }

    void funcB() {
        lock.unlock(); // 同期終了
        ...
    }
}

ただし、柔軟になった代わりにリスクは高くなります。
プログラマは、unlock 漏れがないように細心の注意を払う必要があります。

もし synchronized文で済むのであれば、わざわざ Lockインタフェースを使う必要はないでしょう。

関連:synchronizedの多いコードは危険信号

try (final var executor = Executors.newFixedThreadPool(3)) {
    final Lock lock = new ReentrantLock();

    for (int i = 1; i <= 3; i++) {
        final var id = i;
        TimeUnit.MILLISECONDS.sleep(100);

        executor.submit(() -> {
            System.out.println(id + " : lock ...");
            lock.lock();
            try {
                System.out.println(id + " : lock OK!");

                TimeUnit.SECONDS.sleep(2);
                System.out.println(id + " : sleep completed (unlock!)");
            } catch (InterruptedException e) {
                System.out.println("InterruptedException!");
            } finally {
                lock.unlock();
            }
        });
    }
}

// 結果
// ↓
//1 : lock ...
//1 : lock OK!
//2 : lock ...
//3 : lock ...
//1 : sleep completed (unlock!)
//2 : lock OK!
//2 : sleep completed (unlock!)
//3 : lock OK!
//3 : sleep completed (unlock!)

メソッド

void lock ()

ロックを取得します。

try (final var executor = Executors.newFixedThreadPool(3)) {
    final Lock lock = new ReentrantLock();

    for (int i = 1; i <= 3; i++) {
        final var id = i;
        TimeUnit.MILLISECONDS.sleep(100);

        executor.submit(() -> {
            System.out.println(id + " : lock ...");
            lock.lock();
            try {
                System.out.println(id + " : lock OK!");

                TimeUnit.SECONDS.sleep(2);
                System.out.println(id + " : sleep completed (unlock!)");
            } catch (InterruptedException e) {
                System.out.println("InterruptedException!");
            } finally {
                lock.unlock();
            }
        });
    }
}

// 結果
// ↓
//1 : lock ...
//1 : lock OK!
//2 : lock ...
//3 : lock ...
//1 : sleep completed (unlock!)
//2 : lock OK!
//2 : sleep completed (unlock!)
//3 : lock OK!
//3 : sleep completed (unlock!)

void lockInterruptibly ()

現在のスレッドに対して割り込みが発生していないかぎり、ロックを取得します。

try (final var executor = Executors.newFixedThreadPool(3)) {
    final Lock lock = new ReentrantLock();

    for (int i = 1; i <= 3; i++) {
        final var id = i;
        TimeUnit.MILLISECONDS.sleep(100);

        executor.submit(() -> {
            try {
                System.out.println(id + " : lock ...");
                lock.lockInterruptibly();
                try {
                    System.out.println(id + " : lock OK!");

                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(id + " : sleep completed (unlock!)");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                System.out.println("InterruptedException!");
            }
        });
    }
}

// 結果
// ↓
//1 : lock ...
//1 : lock OK!
//2 : lock ...
//3 : lock ...
//1 : sleep completed (unlock!)
//2 : lock OK!
//2 : sleep completed (unlock!)
//3 : lock OK!
//3 : sleep completed (unlock!)
try (final var executor = Executors.newFixedThreadPool(2)) {
    final Lock lock = new ReentrantLock();

    class Task implements Runnable {
        private final String name;

        Task(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            try {
                System.out.println(name + " : lock ...");
                lock.lockInterruptibly();
                try {
                    System.out.println(name + " : lock OK!");

                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(name + " : sleep completed (unlock!)");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                System.out.println(name + " : InterruptedException!");
            }
        }
    }

    executor.submit(new Task("A"));
    TimeUnit.MILLISECONDS.sleep(100);

    final var futureB = executor.submit(new Task("B"));
    TimeUnit.MILLISECONDS.sleep(100);

    System.out.println("cancel task(B)");
    futureB.cancel(true);
}

// 結果
// ↓
//A : lock ...
//A : lock OK!
//B : lock ...
//cancel task(B)
//B : InterruptedException!
//A : sleep completed (unlock!)

Condition newCondition ()

このLockインスタンスにバインドされた新しいConditionインスタンスを返します。

// 基準となる時刻
final long current = System.nanoTime();

// 基準となる時刻からの差分を秒として取得
final DoubleSupplier elapsedTime = () -> (System.nanoTime() - current) / 1000000000.0;

try (var executor = Executors.newSingleThreadExecutor()) {
    final Lock lock = new ReentrantLock();
    final var cond = lock.newCondition();

    executor.submit(() -> {
        System.out.printf("lock ... (%f sec.)%n", elapsedTime.getAsDouble());
        lock.lock();
        try {
            System.out.printf("await ... (%f sec.)%n", elapsedTime.getAsDouble());
            cond.await();
            System.out.printf("await OK! (%f sec.)%n", elapsedTime.getAsDouble());
        } catch (InterruptedException e) {
            System.out.println("InterruptedException!");
        } finally {
            lock.unlock();
        }
    });

    TimeUnit.SECONDS.sleep(5);

    lock.lock();
    try {
        System.out.printf("signal! (%f sec.)%n", elapsedTime.getAsDouble());
        cond.signal();
    } finally {
        lock.unlock();
    }
}

// 結果
// ↓
//lock ... (0.003795 sec.)
//await ... (0.006260 sec.)
//signal! (5.019196 sec.)
//await OK! (5.019534 sec.)

boolean tryLock ()

呼出し時にロックされていない場合にのみ、ロックを取得します。

// 基準となる時刻
final long current = System.nanoTime();

// 基準となる時刻からの差分を秒として取得
final DoubleSupplier elapsedTime = () -> (System.nanoTime() - current) / 1000000000.0;

try (var executor = Executors.newFixedThreadPool(2)) {
    final Lock lock = new ReentrantLock();

    executor.submit(() -> {
        lock.lock();
        try {
            System.out.println("A : lock OK!");

            TimeUnit.SECONDS.sleep(5);
            System.out.println("A : sleep completed (unlock!)");
        } catch (InterruptedException e) {
            System.out.println("InterruptedException!");
        } finally {
            lock.unlock();
        }
    });

    TimeUnit.MILLISECONDS.sleep(100);

    executor.submit(() -> {
        try {
            while (true) {
                if (lock.tryLock()) {
                    try {
                        System.out.printf("B : tryLock = true (%f sec.)%n",
                                elapsedTime.getAsDouble());
                        break;
                    } finally {
                        lock.unlock();
                    }
                }

                System.out.printf("B : tryLock = false (%f sec.)%n",
                        elapsedTime.getAsDouble());
                TimeUnit.SECONDS.sleep(2);
            }
        } catch (InterruptedException e) {
            System.out.println("InterruptedException!");
        }
    });
}

// 結果
// ↓
//A : lock OK!
//B : tryLock = false (0.113863 sec.)
//B : tryLock = false (2.125517 sec.)
//B : tryLock = false (4.135200 sec.)
//A : sleep completed (unlock!)
//B : tryLock = true (6.139156 sec.)

boolean tryLock (long time, TimeUnit unit)

指定された待機時間内でロックが利用可能であり、現在のスレッドで割り込みが発生していない場合に、ロックを取得します。

// 基準となる時刻
final long current = System.nanoTime();

// 基準となる時刻からの差分を秒として取得
final DoubleSupplier elapsedTime = () -> (System.nanoTime() - current) / 1000000000.0;

try (var executor = Executors.newFixedThreadPool(2)) {
    final Lock lock = new ReentrantLock();

    executor.submit(() -> {
        lock.lock();
        try {
            System.out.println("A : lock OK!");

            TimeUnit.SECONDS.sleep(5);
            System.out.println("A : sleep completed (unlock!)");
        } catch (InterruptedException e) {
            System.out.println("InterruptedException!");
        } finally {
            lock.unlock();
        }
    });

    TimeUnit.MILLISECONDS.sleep(100);

    executor.submit(() -> {
        try {
            while (true) {
                if (lock.tryLock(2, TimeUnit.SECONDS)) {
                    try {
                        System.out.printf("B : tryLock = true (%f sec.)%n",
                                elapsedTime.getAsDouble());
                        break;
                    } finally {
                        lock.unlock();
                    }
                }

                System.out.printf("B : tryLock = false (%f sec.)%n",
                        elapsedTime.getAsDouble());
            }
        } catch (InterruptedException e) {
            System.out.println("InterruptedException!");
        }
    });
}

// 結果
// ↓
//A : lock OK!
//B : tryLock = false (2.114785 sec.)
//B : tryLock = false (4.131868 sec.)
//A : sleep completed (unlock!)
//B : tryLock = true (5.011976 sec.)

void unlock ()

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

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


関連記事

ページの先頭へ