1. ホーム
  2. java

[解決済み] synchronized(this)が使えるのに、なぜReentrantLockを使うのですか?

2022-03-16 11:11:28

質問

を使用することができる場合、同時実行におけるロックがそれほど重要である理由を理解しようとしているのです。 synchronized (this) . 以下のダミーコードでは、どちらも可能です。

  1. メソッド全体を同期させるか、脆弱な部分を同期させるか( synchronized(this){...} )
  2. または、脆弱なコード領域をReentrantLockでロックする。

コード

    private final ReentrantLock lock = new ReentrantLock(); 
    private static List<Integer> ints;

    public Integer getResult(String name) { 
        .
        .
        .
        lock.lock();
        try {
            if (ints.size()==3) {
                ints=null;
                return -9;
            }   

            for (int x=0; x<ints.size(); x++) {
                System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
            }

        } finally {
            lock.unlock();
        } 
        return random;
}

解決方法は?

A リエントラントロック 非構造化 とは異なり synchronized つまり、ロックにブロック構造を使う必要はなく、メソッドをまたいでロックを保持することも可能です。例を挙げよう。

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

このようなフローは synchronized を構成します。


それはさておき。 ReentrantLock サポート ロックポーリング タイムアウトをサポートする割り込み可能なロック待機 . ReentrantLock にも対応しています。 設定可能 公平性 ポリシー より柔軟なスレッドスケジューリングが可能になります。

<ブロッククオート

このクラスのコンストラクタには、オプションで 公平性 パラメータがあります。設定時 true 競合がある場合、ロックは最も長く待機しているスレッドにアクセスを許可することを優先します。そうでない場合は、このロックは特定のアクセス順序を保証しない。多くのスレッドからアクセスされるフェアロックを使用するプログラムは、 デフォルトの設定を使用するプログラムよりも全体のスループットが低下する (つまり、遅くなる、しばしばかなり遅くなる) かもしれませんが、ロックを取得する時間のばらつきが小さく、飢餓がないことが保証されます。ただし、ロックの公平性は、スレッドスケジューリングの公平性を保証するものではありません。したがって、公平なロックを使用している多くのスレッドのうちの1つは、他のアクティブなスレッドが進行しておらず、現在ロックを保持していない間に、連続して複数回ロックを取得することができます。また、時間制限のない tryLock メソッドは公平性の設定を尊重しません。他のスレッドが待っていても、ロックが利用可能であれば成功します。


ReentrantLock よろしい もあります。 よりスケーラブルに より高いコンテンションでより良いパフォーマンスを発揮します。これについては、以下の記事をご覧ください。 こちら .

しかし、この主張には異論もあり、以下のコメントをご覧ください。

<ブロッククオート

リエントラントロックテストでは、毎回新しいロックが作成されるため、排他的ロックはなく、結果のデータは無効です。また、IBMのリンクは基礎となるベンチマークのソースコードを提供していないため、テストが正しく実施されたかどうかを判断することは不可能です。


どのような場合に ReentrantLock s? developerWorksの記事によると...

答えはとても簡単で、実際に必要なときに使うものです。 synchronized 例えば、時間指定ロック待機、割り込み可能ロック待機、非ブロック構造ロック、複数条件変数、ロックポーリングなどです。 ReentrantLock にもスケーラビリティの利点があり、実際に高いコンテンションを示す状況がある場合は使用すべきですが、大半の synchronized ブロックは、高いコンテンションどころか、コンテンションが発生することもほとんどありません。私は、同期が不十分であることが証明されるまでは、同期を使用して開発することをお勧めします。 ReentrantLock . これらは上級者のための高度なツールであることを忘れないでください。(そして、本当に上級のユーザーは、シンプルなツールが不十分であると確信するまでは、見つけうる限り最もシンプルなツールを好む傾向があります)。いつものように、まず正しいものを作り、それから速くする必要があるかどうかを心配することです。


もうひとつ、近い将来、より重要な意味を持つようになるであろう、以下の事柄に関連しています。 Java 15とProject Loom . 仮想スレッドの(新しい)世界では、基本的なスケジューラは、よりよく動作するようになります ReentrantLock でできることよりも synchronized 少なくともJava 15の初期リリースではその通りですが、後に最適化される可能性があります。

<ブロッククオート

現在のLoomの実装では、仮想スレッドを固定できるのは、スタック上にネイティブフレームがある場合(Javaコードがネイティブコード(JNI)を呼び出し、それがJavaにコールバックされる場合)、および synchronized ブロックやメソッドで使用されます。これらの場合、仮想スレッドをブロックすると、それを担う物理スレッドもブロックされます。ネイティブコールが完了するか、モニターが解放されると ( synchronized ブロック/メソッドが終了する)スレッドの固定が解除されます。

<ブロッククオート

によってガードされた共通のI/Oオペレーションがある場合、そのI/Oオペレーションを実行するために synchronized に置き換えると、モニターは ReentrantLock を使えば、モニターによるピン留めを修正する前に、 Loom のスケーラビリティの恩恵を十分に受けることができます (あるいは、より性能の高い StampedLock 可能であれば)。