Javaで一般的に使用されるいくつかのロック

転載元:https//www.cnblogs.com/jyroy/p/11365935.html

1つは、悲観的ロックと楽観的ロックです。

ペシミスティックロック:同じデータに対する同時操作の場合、ペシミスティックロックは、データを使用するときにデータを変更する他のスレッドが必要であると考えているため、データを取得するときに最初にロックして、データが他のスレッドによって変更されないようにします。Javaでは、synchronizedキーワードとLockの実装クラスはどちらも悲観的なロックです。

オプティミスティックロック:オプティミスティックロックは、データの使用時に他のスレッドがデータを変更しないと見なすため、ロックを追加しません。データを更新する前に、他のスレッドがデータを更新したかどうかを判断するだけです。このデータが更新されていない場合、現在のスレッドは変更されたデータを正常に書き込みます。データが他のスレッドによって更新されている場合、さまざまな実装に応じてさまざまな操作(エラー報告や自動再試行など)が実行されます。

上記の概念の説明によると、次のことがわかります。

  • ペシミスティックロックは、書き込み操作が多いシナリオに適しています。最初にロックすると、書き込み操作中にデータが正しいことを確認できます。

  • オプティミスティックロックは、多くの読み取り操作があるシナリオに適しており、ロックなしの機能により、読み取り操作のパフォーマンスを大幅に向上させることができます。

以下に例を書いて確認してください。

public class Test {
    public int number = 1;
    /**  ------------------------ 悲观锁实现 start-------------------**/
    // 方式一:使用synchronized关键字
    public synchronized void testMethod(){
        number ++;// 操作同步资源
    }
    // 方式二:使用lock锁,需要保证多个线程使用同一个锁
    private ReentrantLock lock = new ReentrantLock();
    public void modify(){
        lock.lock();
        number ++;// 操作同步资源
        lock.unlock();
    }
    /**  ------------------------ 悲观锁实现 end-------------------**/

    /**  ------------------------ 乐观锁实现 start-------------------**/
    // java.util.concurrent包中的原子类
    private AtomicInteger atomicInteger = new AtomicInteger();
    public int increment(){
        return atomicInteger.incrementAndGet();// 执行自增1
    }
    /**  ------------------------ 乐观锁实现 end-------------------**/
}

同時リクエストをシミュレートする

package com.xiateng;

public class MyThread extends Thread{
    private Test test;
    public MyThread(Test test){
        this.test = test;
    }
    @Override
    public void run(){
        try {
            Thread.sleep(1000);// 模拟逻辑处理时间
            int increment = test.increment();
            System.out.println("number = "+ increment);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

 10スレッドを開始して、リソーステストを処理します。

class test1{
    public static void main(String[] args) {
        Test test = new Test();
        for (int i = 1; i < 10; i++){
            MyThread thread = new MyThread(test);
            new Thread(thread).start();
        }
    }
}

ロックを使用しない場合:スレッドを順番に実行できず、データがダーティになります

ペシミスティックロックを使用する:明示的なロック後に同期リソースを操作する

楽観的ロックを使用する:同期リソースを直接操作する

楽観的なロックが同期リソースを直接操作してスレッドの同期を実現できるのはなぜですか?

楽観的ロックはCASを通じて実現されます。CASのフルネームはCompareAnd Swap(Compare And Swap)で、これはロックフリーアルゴリズムです。ロックを使用せずに複数のスレッド間の可変同期を実現します(スレッドはブロックされません)。

CASアルゴリズムには、次の3つのオペランドが含まれます。

  • 読み取りと書き込みが必要なメモリ値V。

  • 比較する値A。

  • 書き込まれる新しい値B。

Vの値がAに等しい場合にのみ、CASは新しい値Bを使用してVの値をアトミックに更新します(比較+更新は全体としてアトミック操作です)。それ以外の場合、CASは操作実行しません。一般に、「更新」は常に再試行される操作です。

ソースコードは次のとおりです。

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = this.getIntVolatile(o, offset);
        } while(!this.compareAndSwapInt(0, offset, v, v + delta));

        return var5;
    }

OpenJDK 8のソースコードによると、getAndAddInt()ループが指定されたオブジェクトoのオフセットで値vを取得し、メモリ値がvに等しいかどうかを判断することがわかります。それらが等しい場合、メモリ値はv + deltaに設定され、そうでない場合はfalseを返し、設定が成功するまでループは再試行を続け、ループを終了して古い値を返します。「compare + update」操作全体は、JNIのCPU命令によって完了するcompareAndSwapInt()にカプセル化されます。これはアトミック操作であり、複数のスレッドが同じ変数の変更された値を確認できるようにします。

後続のJDKは、CPUのcmpxchg命令を使用して、レジスタ内のAをメモリ内の値Vと比較します。それらが等しい場合は、メモリに書き込まれる新しい値Bを格納します。それらが等しくない場合、メモリ値Vがレジスタの値Aに割り当てられます。次に、Javaコードのwhileループを介してcmpxchg命令を再度呼び出し、設定が成功するまで再試行します。

CASは非常に効率的ですが、次の3つの問題があります。

1. ABA問題、CASは値を操作するときにメモリ値が変更されたかどうかを確認する必要があり、変更がない場合はメモリ値を更新しますが、メモリ値の原理がAの場合、Bになり、その後Aになると、CASチェックはメモリ値が変更されていないと見なします。解決策は、変数の前にバージョン番号を追加することです。

2.サイクルタイムが長い場合、オーバーヘッドが比較的大きくなります。CAS操作が長時間失敗すると、常にスピンし、CPUに大きなオーバーヘッドが発生します。

3. 1つの共有変数のアトミック操作のみを保証できます。複数の共有変数がある場合、CASは操作のアトミック性を保証できません。

2.スピンロックとアダプティブスピンロック

では、スピンロックとは何ですか?

Javaスレッドをブロックまたはウェイクアップするには、オペレーティングシステムがCPU状態を切り替えて完了する必要があります。状態の切り替えにはプロセッサ時間が必要です。同期されたコードブロックのロジック処理が比較的単純な場合、状態の切り替えにコード実行時間よりも時間がかかる場合があります。 。利益は損失の価値がありません。

現在のスレッドを「しばらく待つ」には、現在のスレッドをスピンする必要があります。スピンの完了後に前のロックが解放されている場合、現在のスレッドはブロックせずに直接ロックを取得できるため、切り替えを回避できます。スレッドオーバーヘッド、これはスピンロックです。

長所と短所:スピン待機はスレッド切り替えのオーバーヘッドを回避しますが、プロセッサ時間を消費します。ロックが短時間占有されると、スピン効果は非常に良好になります。逆に、ロックが長時間占有されると、時間が経過すると、セルフスピニングスレッドはプロセッサリソースを無駄にするだけなので、スピン待機時間には一定の制限が必要です。スピンが制限された回数を超えてロックが正常に取得されない場合、現在のスレッドは一時停止されます。

スピンロックはJDK1.4.2で導入されました。-XX:+ UseSpinning使用してオンにします。JDK 6では、デフォルトでオンになっており、アダプティブスピンロック(アダプティブスピンロック)が導入されています。

アダプティブとは、スピンの時間(回数)が固定されなくなったことを意味しますが、同じロックでの前のスピン時間とロック所有者の状態によって決定されます。同じロックオブジェクト上で、スピン待機がロックを正常に取得し、ロックを保持しているスレッドが実行されている場合、仮想マシンはこのスピンも再び成功する可能性が高いと見なし、スピンを許可します待機は続きます比較的長い時間。特定のロックでスピンが正常に取得されない場合は、将来ロックを取得しようとするときにスピンプロセスを省略し、プロセッサリソースの浪費を避けるために、スレッドを直接ブロックすることができます。

3つ目は、同期の4つの進化段階です。ロックなし->バイアスロック->軽量ロック->重量ロック

1、同期

前書き

Synchronizedは、jvmレベルのキーワードであり、特定のコードを同期できます。ReentrantLockとは異なり、中断することはできません。

モニター

モニターは、同期ツールまたは同期メカニズムとして理解でき、通常はオブジェクトとして説明されます。すべてのJavaオブジェクトには、内部ロックまたはモニターロックと呼ばれる非表示のロックがあります。

モニターはスレッド専用のデータ構造です。各スレッドには、使用可能なモニターレコードのリストと、使用可能なモニターレコードのグローバルリストがあります。ロックされた各オブジェクトはモニターに関連付けられており、モニターには、ロックを所有するスレッドの一意の識別子を格納する所有者フィールドがあり、ロックがこのスレッドによって占有されていることを示します。

同期されたスレッドの同期は、基盤となるオペレーティングシステムのミューテックスロック(相互排除ロック)に依存してスレッドの同期を実現するモニターを介して実現されます。

Javaオブジェクトヘッダー

オブジェクトヘッダーには、主に2つのデータ部分が含まれています。MarkWord(マークされたフィールド)、Klass Pointer(タイプポインター)です。

マークワード:オブジェクトのHashCode、世代経過時間、およびロックフラグ情報はデフォルトで保存されます。(マークワードに保存されているコンテンツとロックフラグの関係は以下のとおりです)

Klass Point:クラスメタデータへのオブジェクトのポインター。仮想マシンはこのポインターを使用して、このオブジェクトがどのクラスインスタンスであるかを判別します。
ここに画像の説明を挿入します
JDK1.6より前に同期:非スピンロック(ミューテックス)を使用する場合、「Javaスレッドをブロックまたはウェイクアップするには、オペレーティングシステムがCPU状態を完了に切り替える必要があり、この状態遷移にはプロセッサ時間が必要です。コードブロックを同期する場合の内容が単純すぎて、状態遷移にかかる時間がユーザーコードの実行時間より長くなる可能性があります。」この方法は、同期が最初に実現された同期の方法であり、これがJDK6より前の同期の効率が低い理由です。オペレーティングシステムに依存するこの種のロックMutexLockは「ヘビーウェイトロック」と呼ばれます。ロックの取得と解放のパフォーマンスコストを削減するために、JDK6では「バイアスロック」と「ライトウェイトロック」が導入されました。

2.ロックなし

ロックなしはリソースをロックしません。すべてのスレッドが同じリソースにアクセスして変更できますが、同時に正常に変更できるのは1つのスレッドだけです。

ロックフリーの機能は、変更操作がループで実行され、スレッドが常に共有リソースの変更を試みることです。競合がない場合、変更は成功して終了します。それ以外の場合、ループは試行を続けます。複数のスレッドが同じ値を変更する場合、1つのスレッドがそれを正常に変更できる必要があり、変更に失敗した他のスレッドは、変更が成功するまで再試行を続けます。上で紹介したCASの原理と応用は、ロックフリーの実現です。ロックフリーは完全にロックに置き換えることはできませんが、状況によってはロックフリーのパフォーマンスが非常に高くなります。

3.バイアスロック

バイアスされたロックとは、同期されたコードの一部がスレッドによってアクセスされたことを意味します。その後、スレッドは自動的にロックを取得し、ロックを取得するコストを削減します。

ほとんどの場合、ロックは常に同じスレッドによって複数回取得され、マルチスレッドの競合がないため、偏ったロックが発生します。目標は、1つのスレッドのみが同期されたコードブロックを実行する場合のパフォーマンスを向上させることです。

スレッドが同期コードブロックにアクセスしてロックを取得すると、ロックバイアスのスレッドIDがマークワードに格納されます。スレッドが同期ブロックに出入りするとき、CAS操作はロックとロック解除に使用されなくなりましたが、MarkWordが現在のスレッドを指すバイアスされたロックを格納しているかどうかを確認します。バイアスロックの導入は、マルチスレッドの競合なしに不要な軽量ロック実行パスを最小限に抑えることです。軽量ロックの取得と解放は複数のCASアトミック命令に依存し、バイアスロックはThreadIDを置き換えるだけでよいためです。CASアトミック命令に依存するだけです。一度。

バイアスされたロックは、他のスレッドがバイアスされたロックを競合しようとしたときにのみロックを解放し、スレッドはバイアスされたロックをアクティブに解放しません。バイアスされたロックを取り消すには、グローバルセキュリティポイント(バイトコードが実行されていないこの時点)を待つ必要があります。最初に、バイアスされたロックを所有するスレッドを一時停止して、ロックオブジェクトがロックされているかどうかを判断します。バイアスロックを解除した後、ロックなし(フラグビットが「01」)または軽量ロック(フラグビットが「00」)の状態に戻ります。

バイアスロックは、JDK6以降のJVMではデフォルトで有効になっています。JVMパラメータを使用してバイアスロックをオフにすることができます:-XX:-UseBiasedLocking = false。閉じると、プログラムはデフォルトで軽量ロック状態になります。

4.軽量ロック

これは、ロックがバイアスされたロックであり、別のスレッドがアクセスした場合、バイアスされたロックが軽量ロックにアップグレードされ、他のスレッドがブロックせずにスピンの形でロックを取得しようとするため、パフォーマンスが向上することを意味します。

コードが同期ブロックに入るとき、同期オブジェクトのロック状態がロック解除されている場合(ロックフラグが「01」状態であり、バイアスされたロックであるかどうかは「0」です)、仮想マシンは最初に同期オブジェクトを作成します。現在のスレッドのスタックフレームLockRecordという名前のスペースは、ロックオブジェクトの現在のマークワードのコピーを格納し、オブジェクトヘッダーのマークワードをロックレコードにコピーするために使用されます。

コピーが成功すると、仮想マシンはCAS操作を使用して、オブジェクトのマークワードをロックレコードへのポインターに更新し、ロックレコードの所有者ポインターをオブジェクトのマークワードにポイントします。

更新アクションが成功すると、スレッドはオブジェクトのロックを持ち、オブジェクトMark Wordのロックフラグは「00」に設定され、オブジェクトが軽量ロック状態にあることを示します。

軽量ロックの更新操作が失敗した場合、仮想マシンは最初にオブジェクトのマークワードが現在のスレッドのスタックフレームを指しているかどうかを確認します。そうでない場合は、現在のスレッドがすでにこのオブジェクトのロックを所有していることを意味します。 、したがって、同期ブロックに直接入力して続行できます。実行します。それ以外の場合は、複数のスレッドがロックをめぐって競合することを意味します。

待機中のスレッドが1つしかない場合、スレッドは回転して待機します。ただし、スピンが特定の回数を超えた場合、または1つのスレッドがロックを保持し、1つがスピンし、3回目の訪問がある場合、軽量ロックは重量ロックにアップグレードされます。

5.ヘビーウェイトロック

ヘビーウェイトロックにアップグレードすると、ロックフラグのステータス値が「10」になります。このとき、ヘビーウェイトロックへのポインタがマークワードに格納されます。このとき、ロックを待機しているスレッドがブロッキングに入ります。状態。

概要:部分ロックは、Mark Wordを比較することでロックの問題を解決し、CAS操作の実行を回避します。軽量ロックは、CAS操作とスピンを使用してロックの問題を解決し、パフォーマンスに影響を与えるスレッドのブロックとウェイクアップを回避します。ヘビーウェイトロックは、ロックを所有するスレッドを除くすべてのスレッドをブロックします。

4、フェアロックとアンフェアロック

フェアロック

フェアロックとは、複数のスレッドがロックを適用した順序でロックを取得することを意味します。スレッドは直接キューに入り、キューの最初のスレッドがロックを取得できます。フェアロックの利点は、ロックを待機しているスレッドが飢えて死ぬことがないことです。欠点は、全体的なスループット効率が不公平なロックよりも低いことです。最初のスレッドを除く待機キュー内のすべてのスレッドがブロックされ、CPUによってブロックされたスレッドをウェイクアップするオーバーヘッドが不公平なロックよりも大きくなります。

不当なロック

不公平なロックとは、複数のスレッドがロックされているときに直接ロックを取得しようとする場合であり、取得できなくなるまで待機キューが終了するまで待機します。ただし、この時点でロックが利用可能である場合、このスレッドはブロックせずに直接ロックを取得できるため、ロックを適用するスレッドが最初にロックを取得するときに不公平なロックが発生する可能性があります。不公平なロックの利点は、スレッドを呼び出すオーバーヘッドを減らすことができ、スレッドがブロックせずに直接ロックを取得する機会があり、CPUがすべてのスレッドをウェイクアップする必要がないため、全体的なスループット効率が高いことです。不利な点は、待機キュー内のスレッドが飢えて死ぬか、ロックを取得する前に長時間待機する可能性があることです。

次に、ReentrantLockのソースコードを使用して、フェアロックとアンフェアロックについて説明します。

コードによると、ReentrantLockには内部クラスSyncがあります。SyncはAQS(AbstractQueuedSynchronizer)を継承します。ロックの追加と解放の操作のほとんどは、実際にはSyncで実装されます。FairSyncとNonfairSyncの2つのサブカテゴリがあります。ReentrantLockはデフォルトで不公平なロックを使用しますが、コンストラクターを介して公平なロックの使用を明示的に指定することもできます。

 hasQueuedPredecessors()メソッドを入力します

このメソッドは主に、現在のスレッドが同期キューの最初であるかどうかを判断するためのものであることがわかります。最初である場合はtrueを返し、そうでない場合はfalseを返します。

総括する

フェアロックとは、複数のスレッドがキューを同期することにより、ロックを適用する順序でロックを取得することを実現し、それによってフェアネスの特性を実現することです。不公平なロックがロックされている場合、キュー待機は考慮されず、ロックは直接ロックを取得しようとするため、アプリケーションが適用された後にロックが取得される場合があります。

5つの再入可能ロックと非再入可能ロック

リエントラントロックは、再帰ロックとも呼ばれ、同じスレッドが外部メソッドでロックを取得すると、スレッドの内部メソッドが自動的にロックを取得することを意味します(ロックオブジェクトが同じオブジェクトまたはクラスである必要がある場合)。以前に取得したものの、リリースされていないためです。ReentrantLockとJavaで同期されたものはどちらも再入可能ロックです。再入可能ロックの利点の1つは、デッドロックをある程度回避できることです。

次のサンプルコードが分析に使用されます。

 上記のコードでは、クラス内の2つのメソッドは、同期された組み込みロックによって変更され、doOthers()メソッドはdoSomething()メソッドで呼び出されます。組み込みのロックは再入可能であるため、同じスレッドがdoOthers()を呼び出すときに現在のオブジェクトのロックを直接取得し、操作のためにdoOthers()を入力できます。

非再入可能ロックの場合、現在のスレッドは、doOthers()を呼び出す前に、doSomething()中に取得された現在のオブジェクトのロックを解放する必要があります。実際、オブジェクトロックは現在のスレッドによって保持されているため、解放できません。そのため、現時点ではデッドロックが発生します。

再入可能ロックが同期されたリソースを繰り返し呼び出すことができるのはなぜですか?

ReentrantLockを例にとると、ReentrantLockの親クラスであるAQSは、同期ステータスステータスを内部的に維持して、再エントリの数をカウントします。statusの初期値は0です。

スレッドがロックを取得しようとすると、リエントラントロックは最初にステータス値の取得と更新を試みます。status== 0の場合、他のスレッドが同期コードを実行していないことを意味し、ステータスは1に設定され、現在のスレッドが実行を開始します。 。status!= 0の場合、現在のスレッドがロックを取得したスレッドであるかどうかを判断し、そうである場合はstatus + 1を実行すると、現在のスレッドは再びロックを取得できます。非再入可能ロックは、現在のステータス値を直接取得して更新しようとします。status!= 0の場合、ロックの取得に失敗し、現在のスレッドがブロックされます。

ロックが解放されると、現在のスレッドがロックを保持しているスレッドである場合、再入可能ロックも最初に現在のステータスの値を取得します。status-1 == 0の場合、現在のスレッドで繰り返されるすべてのロック取得操作が実行され、スレッドが実際にロックを解放することを意味します。非再入可能ロックは、現在のスレッドがロックを保持しているスレッドであると判断した後、ステータスを直接0に設定し、ロックを解放します。

6つの排他ロックと共有ロック

排他ロックと共有ロックも概念です。最初に特定の概念を紹介し、次にReentrantLockとReentrantReadWriteLockのソースコードを介して排他ロックと共有ロックを紹介します。

排他ロックは排他ロックとも呼ばれます。これは、ロックを一度に1つのスレッドでのみ保持できることを意味します。スレッドTがデータAに排他ロックを追加すると、他のスレッドはAにどのタイプのロックも追加できなくなります。排他ロックを取得するスレッドは、データの読み取りと変更の両方を行うことができます。JDKで同期された実装クラスとJUCでロックされた実装クラスは相互排他ロックです。

共有ロックとは、複数のスレッドがロックを保持できることを意味します。スレッドTがデータAに共有ロックを追加する場合、他のスレッドは共有ロックをAにのみ追加でき、排他ロックを追加することはできません。共有ロックを取得するスレッドは、データの読み取りのみが可能で、データの変更はできません。

排他的ロックと共有ロックもAQSを介して実現されます。さまざまな方法を実装することにより、排他的ロックまたは共有ロックを実現できます。

次の図は、ReentrantReadWriteLockのソースコードの一部を示しています。

 

ReentrantReadWriteLockには、ReadLockとWriteLockの2つのロックがあり、1つは読み取りロック、もう1つは書き込みロックであり、まとめて「読み取り/書き込みロック」と呼ばれます。さらに観察すると、ReadLockとWriteLockは、内部クラスSyncによって実装されたロックであることがわかります。SyncはAQSのサブクラスであり、この構造はCountDownLatch、ReentrantLock、およびSemaphoreにも存在します。

ReentrantReadWriteLockでは、読み取りロックと書き込みロックの本体は同期ですが、読み取りロックと書き込みロックのロック方法は異なります。読み取りロックは共有ロックであり、書き込みロックは排他ロックです。読み取りロックの共有ロックは、同時読み取りが非常に効率的であることを保証でき、読み取りロックと書き込みロックが分離されているため、読み取り、書き込み、読み取り、および書き込みのプロセスは相互に排他的です。したがって、ReentrantReadWriteLockの同時実行性は、一般的なミューテックスロックと比較して大幅に改善されています。

読み取りロックと書き込みロックの特定のロック方法の違いは何ですか?ソースコードを理解する前に、他の知識を確認する必要があります。

AQSについて最初に言及したとき、ロックを保持するスレッドの数を記述するために使用される状態フィールド(int型、32ビット)についても言及しました。

排他ロックでは、この値は通常0または1(再入可能ロックの場合、状態値は再入可能数)であり、共有ロックでは、状態は保持されているロックの数です。ただし、ReentrantReadWriteLockには読み取りと書き込みの2つのロックがあるため、整数変数の状態での読み取りロックと書き込みロック(または状態)の数を記述する必要があります。したがって、状態変数は2つの部分に「ビット単位でカット」され、上位16ビットは読み取りロックステータス(読み取りロックの数)を表し、下位16ビットは書き込みロックステータス(書き込みロックの数)を表します。以下に示すように:

概念を理解した後、コードを見てみましょう。まず、書き込みロックのロックソースコードを見てみましょう。

  • このコードは、最初に現在のロックの数cを取得し、次にcを使用して書き込みロックの数wを取得します。書き込みロックは下位16ビットであるため、下位16ビットの最大値を取得し、現在のc(int w = ExclusiveCount(c);)でAND演算を実行します。演算後、上位16ビットと0はANDになります。は0で、残りは下位ビットです。操作の値は、書き込みロックを保持しているスレッドの数でもあります。

  • 書き込みロックスレッドの数を取得した後、最初に、ロックを保持しているスレッドがあるかどうかを確認します。ロックを保持しているスレッド(c!= 0)が既に存在する場合は、現在の書き込みロックスレッドの数を確認します。書き込みスレッドの数が0(つまり、この時点で読み取りロックがある)の場合、またはスレッドロックを保持することは現在のスレッドではなく、失敗を返します(フェアロックとアンフェアロックの実現を含みます)。

  • 書き込みロックの数が最大数(65535、2の16乗-1)より大きい場合、エラーがスローされます。

  • 書き込みスレッドの数が0で(c!= 0の状況は上記で処理されているため、読み取りスレッドも0である必要があります)、現在のスレッドをブロックする必要がある場合は、失敗を返します。書き込みの数を増やすとCASを介したスレッドは失敗し、失敗も返します。

  • c = 0、w = 0またはc> 0、w> 0(再入可能)の場合、現在のスレッドの所有者を設定するか、ロックして成功を返します。

再入力条件(現在のスレッドは書き込みロックを取得したスレッド)に加えて、tryAcquire()は読み取りロックの存在の判断を追加します。読み取りロックがある場合、書き込みロックの動作が読み取りロックから見えるようにする必要があるため、書き込みロックを取得できません。読み取りロックが取得されたときに書き込みロックを取得できる場合は、他のリーダースレッドが実行されています現在のライタースレッドの動作を認識することは不可能です。

したがって、書き込みロックは、他のリーダースレッドが読み取りロックを解放するのを待った後にのみ、現在のスレッドによって取得できます。書き込みロックが取得されると、他の読み取りスレッドと書き込みスレッドの後続のアクセスはブロックされます。書き込みロックの解放プロセスは、基本的にReentrantLockの解放プロセスと同様です。解放するたびに、書き込み状態が減少します。書き込み状態が0の場合、書き込みロックが解放されたことを意味し、待機中の読み取りスレッドと書き込みスレッドは次のことができます。読み取りおよび書き込みロックへのアクセスを継続します。書き込みスレッドの変更は、後続の読み取りおよび書き込みスレッドに表示されます。

次は、ロックを読み取るためのコードです。

tryAcquireShared(int used)メソッドでは、他のスレッドがすでに書き込みロックを取得している場合、現在のスレッドは読み取りロックの取得に失敗し、待機状態になります。現在のスレッドが書き込みロックを取得するか、書き込みロックが取得されない場合、現在のスレッド(スレッドセーフ、CAS保証に依存)は読み取りステータスを増やし、読み取りロックを正常に取得します。読み取りロックを解放するたびに(スレッドセーフで、複数のリーダースレッドが同時に読み取りロックを解放する場合があります)、読み取りステータスが減少し、減少した値は「1 << 16」です。したがって、読み取り/書き込みロックは読み取りと読み取りのプロセスを共有でき、読み取り、書き込み、読み取り、書き込みのプロセスは相互に排他的です。

この時点で、ミューテックスReentrantLockのフェアロックとアンフェアロックのソースコードを振り返ってみましょう。

 

ReentrantLockにはフェアロックとアンフェアロックがありますが、それらはすべて排他ロックを追加することがわかりました。ソースコードによると、スレッドがlockメソッドを呼び出してロックを取得するときに、同期リソースが他のスレッドによってロックされていない場合、現在のスレッドはCASで状態を正常に更新した後、リソースを正常にプリエンプトします。パブリックリソースが占有されていて、現在のスレッドによって占有されていない場合、ロックは失敗します。したがって、ReentrantLockに追加されたロックは、読み取り操作または書き込み操作に関係なく、排他ロックであると判断できます。

7つの明示的ロックと暗黙的ロック

明示的ロック:ロック暗黙的ロック:同期

明示的ロックと暗黙的ロックの違いは何ですか?

1.さまざまなレベル

同期:JavaのキーワードはJVMによって維持されます。これは、JVMレベルでのロックです。

ロック:これは、JDK5の後にのみ出現した特定のクラスです。ロックの使用は、対応するAPIを呼び出すことです。APIレベルでのロックです

2.さまざまな使用方法

同期:手動でロックおよびロック解除する必要はありません。システムは自動的にロックを解除します。これはシステムによって維持され、論理的な問題がない限り、通常はデッドロックは発生しません。

ロック:手動でロックおよびロック解除する必要があります。手動でロック解除しないとデッドロックが発生します。手動ロックメソッド:lock.lock()。ロックを解除します:ロック解除方法。完了するには、tyr / finallyステートメントブロックと協力する必要があります。

3.待機を中断できるかどうか

同期:例外がスローされるか、実行が完了しない限り、中断することはできません。

ロック:中断できます。set timeoutメソッドtryLock(long timeout、timeUnit unit)を呼び出し、次にlockInterruptibly()をコードブロックに呼び出し、次にinterrupt()メソッドを呼び出して割り込みます

4.それは公正なロックですか

同期:不当なロック

ロック:どちらも大丈夫です。デフォルトは不公平なロックです。その構築メソッドでブール値を渡すことができます。

5.ロックは複数の条件を条件にバインドします

同期:1つのスレッドをランダムにウェイクアップするか、すべてのスレッドをウェイクアップします

ロック:グループウェイクアップによってウェイクアップする必要があり、正確にウェイクアップできるスレッドを実現するために使用されます

6、

 

 

おすすめ

転載: blog.csdn.net/qq_43037478/article/details/111625768