JUC - マルチスレッドの悲観的ロック、楽観的ロック、読み取り/書き込みロック (共有ロック、排他的ロック)、フェアおよびアンフェア ロック、リエントラント ロック、スピン ロック、デッドロック (10)

Javaには主に以下のロックがあります

1. 悲観ロック、楽観ロック

悲観的ロック:現在のスレッドがデータを操作するとき、他のスレッドがデータを変更すると常に考えているため、データを操作するたびにロックされ、同期などのデータを操作するときに他のスレッドがブロックされます。

楽観的ロック: 現在のスレッドがデータを操作するたびに, 他の人がそれを変更しないと思います. 更新するとき, 他の人がデータを更新するかどうかを判断します. バージョンによって判断されます. データが変更されている場合は拒否します. update. たとえば cas は楽観的ロックです. しかし厳密にはロックではありません. Atomicity はデータ同期を保証するために使用されます. たとえば, データベースの楽観的ロックはバージョン管理によって実装されます. Cas はスレッドを保証しません.楽観的には、データ更新中に他のスレッドの影響はありません

概要: 悲観的ロックは書き込み操作が多いシナリオに適しており、楽観的ロックは読み取り操作が多いシナリオに適しており、楽観的ロックのスループットは悲観的ロックよりも高くなります。

2. 共有ロック、排他ロック (読み書きロック、ミューテックス ロック) 

読み書きロックはテクノロジーです: ReentrantReadWriteLockクラスによって実装されます

パフォーマンスを向上させるために、Java は読み取り/書き込みロックを提供します。読み取りには読み取りロックが使用され、書き込みには書き込みロックが使用されます。柔軟な制御。書き込みロックがない場合、読み取りはノンブロッキングになり、プログラムがある程度の実行効率は良いです。

読み取り/書き込みロックは、読み取りロックと書き込みロックに分けられます。複数の読み取りロックは相互に排他的ではなく、読み取りロックと書き込みロックは相互に排他的です。これは、JVM 自体によって制御されます。

読み取りロック (共有ロック) :複数のスレッドが読み取りロックを取得し、同じリソースに同時にアクセスできるようにする

書き込みロック (排他的) :書き込みロックを取得できるスレッドは 1 つだけであり、同じリソースへの同時アクセスは許可されません

共有ロック: 読み取りロックとも呼ばれ、データを表示できますが、データ ロックを変更または削除することはできません。ロック後、他のユーザーは同時に読み取ることはできますが、データを変更、追加、または削除することはできません。このロックは複数のスレッドで使用できます。保留、リソース データ共有用

排他的ロック: 排他的ロック、書き込みロック、排他的ロック、排他的ロックとも呼ばれます。ロックは一度に 1 つのスレッドのみが保持できます。ロック後、再度ロックしようとするスレッドは、現在のスレッドがロック解除されるまでブロックされます。

たとえば、スレッド A がデータに排他ロックを追加すると、他のスレッドはデータにいかなるタイプのロックも追加できなくなり、mutex ロックを取得したスレッドはデータの読み取りと変更の両方が可能になります。

Java の読み書きロック: ReadWriteLock、 ReadWriteLock 実装クラスReentrantReadWriteLock

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 独占锁(写锁) 一次只能被一个线程占有
 * 共享锁(读锁) 多个线程可以同时占有
 * ReadWriteLock;其实现类 ReentrantReadWriteLock
 * 读-读  可以共存!
 * 读-写  不能共存!
 * 写-写  不能共存!
 */
public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        // 写入
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.put(temp + "",temp + "");
            },String.valueOf(i)).start();
        }

        // 读取
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp + "");
            },String.valueOf(i)).start();
        }
    }
}

class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();

    // 读写锁
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock lock = new ReentrantLock();

    // 存、写数据,只希望同时只有一个线程写
    public void put(String key,Object value){
        readWriteLock.writeLock().lock();

        try{
            System.out.println(Thread.currentThread().getName() + "写入" + key);

            map.put(key,value);

            System.out.println(Thread.currentThread().getName() + "写入OK");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    // 取,读数据,所有线程都可以同时读取
    public void get(String key){
        readWriteLock.readLock().lock();

        try{
            System.out.println(Thread.currentThread().getName() + "读取" + key);

            map.get(key);

            System.out.println(Thread.currentThread().getName() + "读取OK");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

出力

1写入1
1写入OK
2写入2
2写入OK
3写入3
3写入OK
4写入4
4写入OK
5写入5
5写入OK
1读取1
2读取2
2读取OK
3读取3
1读取OK
3读取OK
5读取5
5读取OK
4读取4
4读取OK

1-5 データの書き込み時は実行順序が他のスレッドによってプリエンプトされませんが、データの読み取り時はキューにジャンプします 

3. 公平ロックと不公平ロック

Fair lock : ロックを適用する順序でロックを取得する複数のスレッドがあります。つまり、スレッド グループ内の場合、各スレッドがロックを取得することが保証されます。例: ReentrantLock (同期キュー FIFO を使用)

不公平なロック: ロックを取得する方法はランダムです. すべてのスレッドがロックを取得できるという保証はありません. 一部のスレッドは枯渇してロックを取得することはありません. 例: synchronized, ReentrantLock

要約: 不公平なロックのパフォーマンスは公平なロックよりも高く、CPU 時間を再利用できます。

4. リエントラントロック(再帰ロック)

再入可能ロック: 再帰ロックとも呼ばれ、外側のレイヤーがロックを使用した後、内側のレイヤーはデッドロックなしで引き続き使用できます

非再入可能ロック: 現在のスレッドがメソッドを実行してロックを取得した場合、そのメソッドで再度ロックを取得しようとしてもブロックされません。

1. Zhihu からの説明: リエントラント ロックとは、同じスレッドの外側の関数がロックを取得した後も、内側の再帰関数がロックを取得できるコードを指します.同じスレッドが外側のメソッドでロックを取得する場合、内側のメソッドに入るメソッドは自動的にロックを取得します。つまり、スレッドは、既に所有しているコードの同期ブロックに入ることができます。

 2. Coffey の強力な説明: リエントラント ロックはスレッドの単位を指します. スレッドがオブジェクト ロックを取得した後、スレッドはオブジェクトのロックを再度取得できますが、他のスレッドは取得できません.

同期ロックと ReentrantLock はどちらも再入可能ロックです

リエントラント ロックの意味の 1 つは、デッドロックを防ぐことです。

1、同期

/**
 * 可重入锁(递归锁)
 *      在外层使用锁之后,在内层仍然可以使用,并且不会产生死锁(前提是同一把锁,如同一个类、同一个实例、同一个代码块)
 *      1、来自知乎的解释:可重入锁指的是同一个线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。也就是说,线程可以进入任何一个他已经拥有锁的所有同步代码块。
 *      2、Coffey强的解释:可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
 *      synchronized 和 ReentrantLock 都是可重入锁
 *      可重入锁的意义之一在于防止死锁
 * 不可重入锁:在当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞
 */
public class SynchronizedTest {
    public static void main(String[] args) {
        Object obj = new Object();

        new Thread(() -> {
            // 第一次加锁
            synchronized (obj){
                System.out.println(Thread.currentThread().getName() + "线程执行第一层");
                // 第二次加锁,此时obj对象处于锁定状态,但是当前线程仍然可以进入,避免死锁
                synchronized (obj){
                    // 抛异常
                    int a = 10/0;
                    System.out.println(Thread.currentThread().getName() + "线程执行第二层");
                }
            }
        },"t1").start();

        new Thread(() -> {
            // 第一次加锁
            synchronized (obj){
                System.out.println(Thread.currentThread().getName() + "线程执行第一层");
                // 第二次加锁,此时obj对象处于锁定状态,但是当前线程仍然可以进入,避免死锁
                synchronized (obj){
                    System.out.println(Thread.currentThread().getName() + "线程执行第二层");
                }
            }
        },"t2").start();
    }
}

t1 スレッドの第 2 層で例外が発生しました.synchronized キーワードを使用します.例外が発生した場合、ロックは自動的に解放され、t2 スレッドは正常に出力されます. 

2、リエントラントロック

/**
 * 可重入锁(递归锁)
 *      synchronized 和 ReentrantLock 都是可重入锁
 *      可重入锁的意义之一在于防止死锁
 */
public class ReentrantLockTest {
    public static void main(String[] args) {
        // 非公平锁
        Lock lock = new ReentrantLock(false);

        new Thread(() -> {
            // 第一次加锁
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName() + "线程执行第一层");

                // 第二次加锁,此时obj对象处于锁定状态,但是当前线程仍然可以进入,避免死锁
                lock.lock();
                try {
                    // 抛异常
                    int a = 10/0;
                    System.out.println(Thread.currentThread().getName() + "线程执行第二层");
                } finally {
                    lock.unlock();
                }
            } finally { // Lock是显式锁,必须手动关闭锁(忘记关闭锁会导致 死锁)
                lock.unlock();
            }
        },"t1").start();

        new Thread(() -> {
            // 第一次加锁
            lock.lock();
            try{
                System.out.println(Thread.currentThread().getName() + "线程执行第一层");

                // 第二次加锁,此时obj对象处于锁定状态,但是当前线程仍然可以进入,避免死锁
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "线程执行第二层");
                } finally {
                    lock.unlock();
                }
            } finally { // Lock是显式锁,必须手动关闭锁(忘记关闭锁会导致 死锁)
                lock.unlock();
            }
        },"t2").start();
    }
}

t1 スレッドの第 2 層に例外があり、t2 スレッドの通常の出力には影響しない解放ロックが最後に書き込まれます。 

ノート:

1.ロックは明示的なロックであり、ロックは手動で閉じる必要があります(手動でロックを開閉すると、ロックを閉じるのを忘れるとデッドロックが発生します)同期は暗黙的なロックであり、自動的にロックが解除され、自動的に解放され         ます対象外のとき

2. ロックは手動で閉じて解放する必要があり、finally 句で解放する必要があります

ReentrantLock クラスのソース コード コメント

五、スピンロック

スピンロック: スレッドがロックを取得するとき、他のスレッドがロックを取得している場合、そのスレッドはループで待機し、引き続きロックの取得に成功したかどうかを判断し、ロックが取得されるまでループを抜けません。ロックを取得できる実行ユニットは 1 つだけです。

要約: スレッド状態の切り替えがなく、常にユーザー状態になるため、スレッド コンテキスト切り替えの消費が減少します. 欠点は、ループが CPU を消費することです.

6、重量ロック、軽量ロック

重いロックがタイトルです。同期はオブジェクト内のモニター ロック (モニター) によって実装され、モニター ロック自体は、実装するオペレーティング システムのミューテックス ロックに依存します。

オペレーティング システムは、スレッドを切り替えるためにユーザー モードからコア モードに切り替える必要があり、これには非常にコストがかかります。オペレーティング システムのミューテックス ロックの実装に依存するこの種のロックは、重量ロックと呼ばれます。同期を最適化するために、軽量ロックとバイアス ロックが導入されました。

Java の重量ロック: 同期

軽量ロックは、 JDK6 で追加されたロック最適化メカニズムです。軽量ロックは CAS 操作を使用して、同期で使用されるミューテックスを競合なしで排除します。軽量ロックは、オペレーティング システムのミューテックスを使用して実装された重量ロックに相対的です。軽量ロックは、オペレーティング システムのミューテックスを使用して、マルチスレッド競合なしで、従来の重量ロックのパフォーマンス消費を削減します。3 つ以上のスレッドが同じロックに対して競合する場合、軽量ロックは効果がなく、重量ロックに拡張する必要があります。

利点: 競合がなければ、mutex を使用するオーバーヘッドは CAS 操作によってうまく回避されます。

短所: 競合がある場合、mutex 自体のオーバーヘッドに加えて、追加の CAS 操作のオーバーヘッドが発生するため、競合の場合、軽量ロックは従来の重量ロックよりも遅くなります。

セブン、バイアスロック

バイアス ロックは、JDK6 で追加されたロック最適化メカニズムです。競合がない場合、同期全体が排除され、CAS 操作も実行されません。部分性とは偏心性を指し、ロックを取得する最初のスレッドにバイアスをかけることを意味します. 次の実行プロセスで他のスレッドがロックを取得していない場合、バイアスをかけられたロックを保持しているスレッドは永久にロックされます.同期が必要です。バイアスされたロックを保持しているスレッドが、このロックに関連する同期ブロックに入るたびに、仮想マシンは同期操作 (Mark Word でのロック、ロック解除、および更新操作など) を実行できなくなります。

利点: CAS 操作が行われなくても、同期全体が不要になるため、軽量ロックよりも優れています。

短所: プログラム内のほとんどのロックが常に複数の異なるスレッドによってアクセスされる場合、偏ったロックは冗長になります。

8、セグメントロック

セグメント ロックはメカニズムです。セグメント ロックを説明する最良の例は、ConcurrentHashMap です。ConcurrentHashMap の原理: セグメント (Segment) と呼ばれるいくつかの小さな HashMap を内部で細分化します。デフォルトでは、ConcurrentHashMap はさらに 16 のセグメントに分割されます。これはロックの同時実行性です。ConcurrentHashMap にキー値を追加する必要がある場合は、HashMap 全体をロックするのではなく、まずハッシュコードに従ってキー値を格納するセグメントを取得し、次にセグメントをロックして、put 操作を完了します。マルチスレッド環境では、複数のスレッドが同時に put 操作を実行する場合、追加されたキー値が同じセグメントに格納されない限り、スレッドは完全に並列になります。

スレッド セーフ: ConcurrentHashMap はセグメント配列であり、セグメントは ReentrantLock を継承することによってロックされるため、ロックする必要がある各操作はセグメントをロックします。各セグメントがスレッド セーフである限り、グローバル スレッド セーフです。

9、ミューテックス、同期ロック

Mutex ロックは、悲観的ロックおよび排他的ロックと同義です。つまり、リソースには 1 つのスレッドのみがアクセスでき、他のスレッドはアクセスできません。

読み取り読み取りミューテックス

読み書きミューテックス

書き込み読み取りミューテックス

書き込み書き込みミューテックス

Java での同期ロック: 同期

同期ロックはミューテックスと同義です。つまり、複数のスレッドが同時に実行され、共有データに同時にアクセスできるスレッドは 1 つだけです。

Java での同期ロック: 同期

10.デッドロック

デッドロックは現象です。スレッド A がリソース x を保持し、スレッド B がリソース y を保持し、スレッド A がスレッド B がリソース y を解放するのを待ち、スレッド B がスレッド A がリソース x を解放するのを待ち、どちらのスレッドも自身のリソースを解放しない場合、2 つのスレッドは互いのリソースを取得できず、デッドロックが発生します。

Java のデッドロックはそれ自体では解決できないため、スレッドがデッドロックした後は、スレッドは応答できません。したがって、デッドロックを回避するために、プログラムの同時実行シーンに注意を払う必要があります。

十一、ロック粗し

ロックの粗大化は最適化手法です。一連の連続操作が同じオブジェクトのロックとロック解除を繰り返し、さらにロック操作がループ本体で発生した場合、スレッドの競合がなくても、相互排他同期操作が頻繁に行われると、不要なパフォーマンスの損失が発生します。そのため、解決策が採用されています。ロックの範囲を操作シーケンス全体の外側に拡張 (粗く) することで、ロックとロック解除の頻度が大幅に減少し、パフォーマンスの損失が減少します。

12.ロック解除

ロックの削除は最適化手法です。ロックを削除するだけです。Java 仮想マシンの実行中に一部の共有データがスレッドによって競合されないことを Java 仮想マシンが検出すると、ロックの除去を実行できます。

共有データがスレッドによって競合されないと判断しますか?

エスケープ解析技術を利用する: オブジェクトのスコープを解析する. オブジェクトがメソッド A で定義され、メソッド B にパラメーターとして渡される場合、それはメソッド エスケープと呼ばれ、他のスレッドからアクセスされる場合は、スレッド エスケープと呼ばれます。

ヒープ上の特定のデータはエスケープされず、他のスレッドによってアクセスされるため、スタック上のデータとして扱うことができ、スレッドに対してプライベートと見なされ、同期ロックは必要ありません

十三、synchronized

Synchronized は Java のキーワードです。メソッドとオブジェクト インスタンスを変更するために使用されます。

同期は、排他的ロック、悲観的ロック、再入可能ロック、不当ロックに属します

1. インスタンス メソッドを操作する場合、ロックされるのはオブジェクトのインスタンス (this) です。

2. 静的メソッドとして使用される場合、Class クラスはロックされます。これは、クラスのグローバル ロックと同等であり、メソッドを呼び出すすべてのスレッドをロックします。

3. synchronized が非 NULL オブジェクト インスタンスに作用する場合、オブジェクトをロックするすべてのコード ブロックがロックされます。It has multiple queues. 複数のスレッドが一緒にオブジェクト モニターにアクセスすると、オブジェクト モニターはこれらのスレッドを異なるコンテナーに格納します。

各オブジェクトには監視オブジェクトがある.ロックは監視オブジェクトを取り合う.コードブロックのロックはコードブロックの前後にmonitorenter,monitorexit命令を追加することで実現する.メソッドのロックはフラグビットで判断する.

十四、ロックとシンクロの違い

ロック: Java のインターフェース、リエントラント ロック、ペシミスティック ロック、排他ロック、ミューテックス ロック、同期ロック

1. ロックは、手動でロックを取得および解放する必要があります。自動と手動の違いみたいなもん

2. Lock はインターフェースであり、synchronized は Java のキーワードであり、synchronized は組み込み言語の実装です。

3. Synchronized は、例外発生時にスレッドが占有していたロックを自動的に解放するため、デッドロック現象が発生しません; 一方、Lock は、例外が発生した場合、unLock() を通じて積極的にロックを解放しないと、デッドロック現象が発生する可能性がありますデッドロック現象が発生するため、Lock を使用する場合は finally ブロックでロックを解除する必要があります。

4. Lock はロックを待っているスレッドを割り込みに応答させることができますが、synchronized はできません。

5. Lock を介して、ロックが正常に取得されたかどうかを知ることができますが、同期はできません。

6. ロックは、読み取り/書き込みロックを実装することで、複数のスレッドの読み取り操作の効率を向上させることができます。

同期の利点:

基本的な同期機能のみが必要な場合は、synchronized を使用します

Lock は、finally ブロックでロックが解放されるようにする必要があります。同期を使用すると、JVM は、例外が発生した場合でも、ロックが自動的に解放されることを保証します。

Lock を使用する場合、特定のスレッド ロックによってどのロック オブジェクトが保持されているかを Java 仮想マシンが認識するのは困難です。

15. ReentrantLock と synchronized の違い

ReentrantLock は Java のクラスです。Lock クラス、再入可能ロック、悲観的ロック、排他ロック、ミューテックス ロック、同期ロックを継承します。

同じ点:

1. 主に共有変数に安全にアクセスする方法の問題を解決する

2. これらはすべて再入可能ロックであり、再帰ロックとも呼ばれます。同じスレッドが同じロックを複数回取得できます。

3. 可視性と原子性というスレッドセーフの 2 つの特性を保証する

違い:

1. ReentrantLock はマニュアル車のようなもので、lock メソッドと unlock メソッドを明示的に呼び出す必要があり、synchronized は暗黙的にリリース ロックを取得します。

2. ReentrantLock は割り込みに応答できますが、synchronized は割り込みに応答できません。

3. ReentrantLock は API レベルであり、synchronized は JVM レベルです

4. ReentrantLock は公平なロックと不公平なロックを実装できます. デフォルトは不公平なロックです. synchronized は不公平なロックであり, 変更することはできません.

5. ReentrantLock は Condition を通じて複数の条件をバインドできます

参照できます

Java の 21 種類のロックを完全に理解する

Javaのロックとは何ですか? _zhangjia_happy のブログ - CSDN ブログ_Java ロックの種類とは?

おすすめ

転載: blog.csdn.net/MinggeQingchun/article/details/127383395