広告

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

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


概要

synchronizedメソッドおよび文を使用してアクセスする暗黙の監視ロックと同じ基本動作およびセマンティックスを使用し、かつ拡張機能を持つ、再入可能な相互排他Lockです。

クラス構成

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

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

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

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

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

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

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

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

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

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

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

try (final var executor = Executors.newFixedThreadPool(3)) {
    final var 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!)

コンストラクタ

ReentrantLock ()

ReentrantLockのインスタンスを生成します。

try (final var executor = Executors.newFixedThreadPool(3)) {
    final var 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!)

ReentrantLock (boolean fair)

指定された公平性ポリシーを使用して、ReentrantLockのインスタンスを作成します。

fair パラメータ以外については ReentrantLock() の使用例もご参照ください。

void testFair(boolean fair) throws InterruptedException {
    final var lock = new ReentrantLock(fair);
    System.out.println("isFair = " + lock.isFair());

    final var order = new ArrayList<Integer>();
    try (final var executor = Executors.newFixedThreadPool(2)) {
        lock.lock();
        try {
            executor.submit(() -> {
                try {
                    lock.lock();
                    order.add(1);
                } finally {
                    lock.unlock();
                }
                try {
                    lock.lock();
                    order.add(2);
                } finally {
                    lock.unlock();
                }
            });

            TimeUnit.MILLISECONDS.sleep(100);

            executor.submit(() -> {
                try {
                    lock.lock();
                    order.add(3);
                } finally {
                    lock.unlock();
                }
            });

            TimeUnit.MILLISECONDS.sleep(100);

        } finally {
            lock.unlock();
        }
    }

    System.out.println("order = " + order);
}
testFair(true);

// 結果
// ↓
//isFair = true
//order = [1, 3, 2]

testFair(false);

// 結果
// ↓
//isFair = false
//order = [1, 2, 3]

メソッド

int getHoldCount ()

現在のスレッドの、このロックに対する保持数を照会します。

final var lock = new ReentrantLock();

System.out.println(lock.getHoldCount()); // 0
System.out.println(lock.isHeldByCurrentThread()); // false

lock.lock();
try {
    System.out.println(lock.getHoldCount()); // 1
    System.out.println(lock.isHeldByCurrentThread()); // true

    lock.lock();
    try {
        System.out.println(lock.getHoldCount()); // 2
        System.out.println(lock.isHeldByCurrentThread()); // true

        lock.lock();
        try {
            System.out.println(lock.getHoldCount()); // 3
            System.out.println(lock.isHeldByCurrentThread()); // true
        } finally {
            lock.unlock();
        }
        System.out.println(lock.getHoldCount()); // 2
        System.out.println(lock.isHeldByCurrentThread()); // true

    } finally {
        lock.unlock();
    }
    System.out.println(lock.getHoldCount()); // 1
    System.out.println(lock.isHeldByCurrentThread()); // true

} finally {
    lock.unlock();
}
System.out.println(lock.getHoldCount()); // 0
System.out.println(lock.isHeldByCurrentThread()); // false

protected Thread getOwner ()

現在このロックを所有しているスレッドを返します。ロックが所有されていない場合はnullを返します。

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

protected Collection<Thread> getQueuedThreads ()

このロックの取得を待機しているスレッドを含むコレクションを返します。

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

final int getQueueLength ()

このロックの取得を待機中のスレッドの推定数を返します。

final var lock = new ReentrantLock();

try (final var executor = Executors.newFixedThreadPool(5)) {
    for (int i = 1; i <= 5; i++) {
        final var id = i;
        TimeUnit.MILLISECONDS.sleep(100);

        executor.submit(() -> {
            System.out.printf("%d : lock ... : queue length = %d (%b)%n",
                    id, lock.getQueueLength(), lock.hasQueuedThreads());

            lock.lock();
            try {
                System.out.printf("%d : lock OK! : queue length = %d (%b)%n",
                        id, lock.getQueueLength(), lock.hasQueuedThreads());

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

// 結果
// ↓
//1 : lock ... : queue length = 0 (false)
//1 : lock OK! : queue length = 0 (false)
//2 : lock ... : queue length = 0 (false)
//3 : lock ... : queue length = 1 (true)
//4 : lock ... : queue length = 2 (true)
//5 : lock ... : queue length = 3 (true)
//1 : sleep completed (unlock!)
//2 : lock OK! : queue length = 3 (true)
//2 : sleep completed (unlock!)
//3 : lock OK! : queue length = 2 (true)
//3 : sleep completed (unlock!)
//4 : lock OK! : queue length = 1 (true)
//4 : sleep completed (unlock!)
//5 : lock OK! : queue length = 0 (false)
//5 : sleep completed (unlock!)

protected Collection<Thread> getWaitingThreads (Condition condition)

このロックに関連付けられた指定の状態を待機中のスレッドを含むコレクションを返します。

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

int getWaitQueueLength (Condition condition)

このロックに関連付けられた指定の状態で待機中のスレッドの推定数を返します。

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

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

        executor.submit(() -> {
            lock.lock();
            try {
                System.out.printf("%d : await ... : wait queue length = %d (%b)%n",
                        id, lock.getWaitQueueLength(cond), lock.hasWaiters(cond));

                cond.await();
                System.out.printf("%d : await OK!  : wait queue length = %d (%b)%n",
                        id, lock.getWaitQueueLength(cond), lock.hasWaiters(cond));
            } catch (InterruptedException e) {
                System.out.println("InterruptedException!");
            } finally {
                lock.unlock();
            }
        });
    }

    TimeUnit.SECONDS.sleep(2);

    for (int i = 0; i < 3; i++) {
        lock.lock();
        try {
            System.out.println("signal!");
            cond.signal();
        } finally {
            lock.unlock();
        }

        TimeUnit.SECONDS.sleep(1);
    }
}

// 結果
// ↓
//1 : await ... : wait queue length = 0 (false)
//2 : await ... : wait queue length = 1 (true)
//3 : await ... : wait queue length = 2 (true)
//signal!
//1 : await OK!  : wait queue length = 2 (true)
//signal!
//2 : await OK!  : wait queue length = 1 (true)
//signal!
//3 : await OK!  : wait queue length = 0 (false)

final boolean hasQueuedThread (Thread thread)

指定されたスレッドがこのロックの取得を待機中かどうかを照会します。

try (var executor = Executors.newSingleThreadExecutor()) {
    final var lock = new ReentrantLock();
    final var main = Thread.currentThread();

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

                TimeUnit.SECONDS.sleep(1);
                System.out.println("A : main is waiting = " + lock.hasQueuedThread(main));

                TimeUnit.SECONDS.sleep(2);
                System.out.println("A : main is waiting = " + lock.hasQueuedThread(main));
                System.out.println("A : unlock!");
            } finally {
                lock.unlock();
            }

            TimeUnit.SECONDS.sleep(2);
            System.out.println("A : main is waiting = " + lock.hasQueuedThread(main));

        } catch (InterruptedException e) {
            System.out.println("InterruptedException!");
        }
    });

    TimeUnit.SECONDS.sleep(2);

    System.out.println("main : lock ...");
    lock.lock();
    try {
        System.out.println("main : lock OK!");

        TimeUnit.SECONDS.sleep(1);
        System.out.println("main : unlock!");
    } finally {
        lock.unlock();
    }
}

// 結果
// ↓
//A : lock OK!
//A : main is waiting = false
//main : lock ...
//A : main is waiting = true
//A : unlock!
//main : lock OK!
//main : unlock!
//A : main is waiting = false

final boolean hasQueuedThreads ()

このロックの取得を待機中のスレッドが存在するかどうかを照会します。

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

boolean hasWaiters (Condition condition)

このロックに関連付けられた指定の状態で待機しているスレッドが存在するかどうかを照会します。

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

final boolean isFair ()

このロックで公平性がtrueに設定されている場合はtrueを返します。

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

boolean isHeldByCurrentThread ()

現在のスレッドがこのロックを保持しているかどうかを照会します。

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

boolean isLocked ()

このロックがいずれかのスレッドにより保持されているかどうかを照会します。

final var lock = new ReentrantLock();

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

        executor.submit(() -> {
            System.out.printf("%d : lock ... : isLocked = %b%n", id, lock.isLocked());
            lock.lock();
            try {
                System.out.printf("%d : lock OK! : isLocked = %b%n", id, lock.isLocked());

                TimeUnit.SECONDS.sleep(2);
                System.out.printf("%d : sleep completed (unlock!)%n", id);
            } catch (InterruptedException e) {
                System.out.println("InterruptedException!");
            } finally {
                lock.unlock();
            }
        });
    }
}
System.out.printf("main : isLocked = %b%n", lock.isLocked());

// 結果
// ↓
//1 : lock ... : isLocked = false
//1 : lock OK! : isLocked = true
//2 : lock ... : isLocked = true
//3 : lock ... : isLocked = true
//1 : sleep completed (unlock!)
//2 : lock OK! : isLocked = true
//2 : sleep completed (unlock!)
//3 : lock OK! : isLocked = true
//3 : sleep completed (unlock!)
//main : isLocked = false

void lock ()

ロックを取得します。

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

void lockInterruptibly ()

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

try (final var executor = Executors.newFixedThreadPool(3)) {
    final var 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 var 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 var 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.)

String toString ()

このロックおよびその状態を識別する文字列を返します。

final var lock = new ReentrantLock();
final var str1 = lock.toString();

// java.util.concurrent.locks.ReentrantLock@20bd8be5[Unlocked]
System.out.println(str1);

lock.lock();
try {
    final var str2 = lock.toString();

    // java.util.concurrent.locks.ReentrantLock@20bd8be5[Locked by thread Test worker]
    System.out.println(str2);
} finally {
    lock.unlock();
}

boolean tryLock ()

呼出し時に別のスレッドにより保持されていない場合にのみ、ロックを取得します。

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

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

try (var executor = Executors.newFixedThreadPool(2)) {
    final var 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 timeout, TimeUnit unit)

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

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

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

try (var executor = Executors.newFixedThreadPool(2)) {
    final var 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 ()

このロックの解放を試みます。

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


関連記事

ページの先頭へ