ReentrantLockの例と原理(差分同期)

AQSシリーズ

1. AQS のコア原則
2. ReentrantLock の原則と例
3. CountDownLatch / セマフォの例と使用シナリオ
4. BlockingQueue の例と使用シナリオ

1. ReentrantLockの原理

ReentrantLock は、 AQSフレームワーク アプリケーションの実装をベースにしており、 JDK1.5が提供するスレッドの同時実行制御の手段であり、 synchronizedキーワードの機能と同様で、スレッドのセキュリティを確保できるミューテックスです。次に、これをインスタンス化すると、構築メソッドReentrantLock (ブール フェア) に公平なパラメータがあり、そのパラメータは公平なロックの場合は true、不公平なロックの場合はfalse
であることがわかります。ReentrantLock再入可能なロックです。つまり、lock() メソッドを複数回呼び出して、繰り返しロックできます。もちろん、この場合、ロックを複数回解除するには、unlock() メソッドを複数回呼び出す必要があります

ReentrantLock は相互排他ロックの一種ですが、公平なロックと不公平なロックをどのように実現するのでしょうか? ReentrantLock の内部では、 Sync
内部クラスが定義されており、AbstractQueuedSynchronizer を継承し、テンプレート モードの実装でもあるいくつかのメソッドを実装します。また、 FairSync (公平なロック) クラスとNonfairSync (不公平なロック) クラスも定義します。どちらもSyncから継承され、間接的にAbstractQueuedSynchronizerに基づいているため、ReentrantLock は公平なロック不公平なロックも実装しますそして、ReentrantLock自体がLockインターフェースを実装して待機と起動を実現します。

2. 公衆トイレに行く(デモ)

これは、 ReentrantLockロック機能を使用するための小さなデモです。それらの多くはチケット販売の例です。複数のウィンドウまたは複数のインターフェイスで 1 つのチケットを販売しますが、繰り返し販売することはできません。次に、ここでは場面を変えて、公衆トイレです。複数のドアです。一度に 1 人だけが使用できます。シーンは次のようなものです。ショッピングモールにはトイレに通じる道が複数ある場合がありますが、同時に使用できるのは 1 人だけです。他の誰かが一緒に使用することに同意しない限り、^_^、おそらく誰も使用しません。今は有資格者ばかりなので、ある時間に来て人が多ければ並ぶことになるが、これは公平な錠前である。

2.1 フェアロック

public class ReentrantLockTest {
    
    
    //表示排到第几个人了
    volatile static int index = 0;
    //卫生间门上有个锁,人民素质高: true, 素质低: false
    static ReentrantLock lock = new ReentrantLock(true);

    public static void main(String[] args) {
    
    
        start();
    }

    /**
     * 开始活动
     */
    private static void start(){
    
    
        //下面实例化来卫生间的路线,有3个路线可以过来
        new Thread(() -> {
    
    
            for(;;){
    
    
                doing(index++);
            }
        }).start();
        new Thread(() -> {
    
    
            for(;;){
    
    
                doing(index++);
            }
        }).start();
        new Thread(() -> {
    
    
            for(;;){
    
    
                doing(index++);
            }
        }).start();
    }

    /**
     * 上厕所这个动作
     */
    private static void doing(int person){
    
    
        lock.lock();
        System.out.println("第 " + person + " 个人去了" + Thread.currentThread().getName() + " 坑位!");
        try {
    
    
            System.out.println("上卫生间中,顺便抽烟!");
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("第 " + person + " 个人完事了!");
        System.out.println("----------------------------");
        lock.unlock();
    }
}

実行結果は以下の通りです。

0 个人去了Thread-0 坑位!
上卫生间中,顺便抽烟!
第 0 个人完事了!
----------------------------1 个人去了Thread-1 坑位!
上卫生间中,顺便抽烟!
第 1 个人完事了!
----------------------------2 个人去了Thread-2 坑位!
上卫生间中,顺便抽烟!
第 2 个人完事了!
----------------------------3 个人去了Thread-0 坑位!
上卫生间中,顺便抽烟!
第 3 个人完事了!
----------------------------4 个人去了Thread-1 坑位!
上卫生间中,顺便抽烟!
第 4 个人完事了!
----------------------------5 个人去了Thread-2 坑位!
上卫生间中,顺便抽烟!

実行結果から、Thread-0、Thread-1、Thread-2 が非常に規則的に交互に実行され、毎回前の人より 1、2、3、4、5 の順序で実行されていることがわかります。これは公平なロックです。 。

2.2 不当なロック

ReentrantLock をインスタンス化するパラメータを false に変更して確認してみましょう。

static ReentrantLock lock = new ReentrantLock(false);

実行結果は以下の通りです。

0 个人去了Thread-0 坑位!
上卫生间中,顺便抽烟!
第 0 个人完事了!
----------------------------3 个人去了Thread-0 坑位!
上卫生间中,顺便抽烟!
第 3 个人完事了!
----------------------------4 个人去了Thread-0 坑位!
上卫生间中,顺便抽烟!
第 4 个人完事了!
----------------------------5 个人去了Thread-0 坑位!
上卫生间中,顺便抽烟!
第 5 个人完事了!
----------------------------6 个人去了Thread-0 坑位!
上卫生间中,顺便抽烟!

実行結果を見ると、1人目と2人目は穴を掴めず、4人目、5人目、6人目もThread-0ルートから来ているので、Thread-1とThread-の人は2 基本的に「窒息」。アップ」。

3. ReentrantLock と Synchronized の違い

違い:

リエントラントロック 同期済み
ReentrantLockはロックを実装するクラスです。 Synchronized は JDK の内部実装であり、キーワードはロックされています
ReentrantLock は、tryLock() を使用してロックの取得を試み、デッドロックを回避し、より安全にします。 同期するとデッドロックの問題が発生する
ReentrantLock は例外を考慮し、最終的にロックを解放する必要があります。{} 同期は異常状態を考慮する必要がありません
ReentrantLock は、それ自体でロックとロック解除の機能を提供しますが、特別な条件下でのロックとロック解除には、Condition オブジェクトの助けが必要です。 Synchronized には Object が提供する wait() が必要で、notify() メソッドは特殊な条件でのロックとロック解除を実現します。
ReentrantLock は粒度が細かくなり、より使いやすくなります。 同期はメソッドまたはコード ブロックのみをロックできます

同じ点:

  1. ReentrantLock と Synchronized はどちらも、スレッドが同じロックを複数回取得できるようにします。
  2. これらは同期的にロックされます。

4. まとめ

実際、現在の JDK8 以降は Synchronized が最適化されており、特に同時実行性が高くない場合は ReentrantLock とほぼ同じになります。同期はロックのない状態から開始され、スレッド数が増加するにつれて偏ったロックになり、軽量ロックにアップグレードされ、最後に重量ロックにアップグレードされます。ただし、一般的な電子商取引プロジェクトや広告プロジェクト、特急配送などはすべて同時実行性の高いプロジェクトであり、このような状況では単一のアプリケーションでは動作しません。より柔軟で便利です。

5.同期(インタビュー延長)

上で述べたように、Synchronized キーワードはロックされていますが、Synchronized キーワードはコード ブロックとして単独で使用することも、メソッドに追加することもできます。コード ブロックに追加すると、このコードがロックされるため、メソッドに追加します。ロックするには何ですか?

Synchronized 同期コード ブロックは一度に 1 つのスレッドによってのみ実行されることがわかっています。小さなデモで試してみましょう。
以下は、start と end で開始し、終了して 5 回出力する静的メソッド mF() です。

public static void mF(){
    
    
    System.out.println("start...");
    synchronized (Object.class){
    
    
        for(int i=0; i<=5; i++){
    
    
            System.out.println("method F!" + Thread.currentThread().getName());
            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
    System.out.println("end...");
}

2 つのスレッドで mF() メソッドを呼び出します。

new Thread(() -> {
    
    
            SynchronizedTest.mF();
        }).start();
        new Thread(() -> {
    
    
            SynchronizedTest.mF();
        }).start();

結果:

start...
start...
method F!Thread-0
method F!Thread-0
method F!Thread-0
method F!Thread-0
method F!Thread-0
method F!Thread-0
end...
method F!Thread-1
method F!Thread-1
method F!Thread-1
method F!Thread-1
method F!Thread-1
method F!Thread-1
end...

2 つの「start...」が出力され、Thread-0 が実行され、Thread-1 が実行されていることがわかります。

Synchronized キーワード読み込みメソッドの場合、クラスには静的メソッドインスタンス メソッドを含めることができることがわかりますが、これら 2 つのメソッドの違いは何でしょうか?
回答:インスタンス ロックとクラス ロックは相互に干渉しません例を書いて見てみましょう。

public synchronized static void mA(){
    
    
    for(;;){
    
    
        System.out.println("method A!");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

public static synchronized void mB(){
    
    
    for(;;){
    
    
        System.out.println("method B!");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

public synchronized void mC(){
    
    
    for(;;){
    
    
        System.out.println("method C!");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

public synchronized void mD(){
    
    
    for(;;){
    
    
        System.out.println("method D!");
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

結果:

method A!
method D!
method C!
method C!
method D!
method A!
method D!
method A!
method C!

実行結果を見ると、メソッド ADC は実行されていますが、メソッド B は実行されていません。要約すると、メソッド
A とメソッド B は両方とも静的メソッドであり、静的メソッドはSynchronizedキーワードでロックされています。は同じロックであるため、実行するメソッドのみがあり、メソッド C と D は両方ともインスタンス メソッドであり、静的メソッド Aに干渉しないため、両方とも実行されます。別のインスタンス メソッドに実行が通知されるのはなぜですか? インスタンス メソッドはSynchronizedキーワードでロックされており、ロックはthisオブジェクトであるため、実行されます。

おすすめ

転載: blog.csdn.net/qq_19283249/article/details/128512367