[並行プログラミング]ロックについてのあなたの理解について話してください

 

0.まえがき

この記事では、Javaの初心者が理解しやすい統合を行い、さまざまなロック用語の恐れを排除し、各ロックの基本的な実装を味わいながら、必要なときに何をチェックするかを理解することを目指しています。まず、ロックは1つのカテゴリにしか属することができないという考えを払拭する必要があります。実際にはそうではありません。たとえば、ロックは、人が男性、医師であるのと同じように、同時にペシミスティックロック、リエントラントロック、フェアロック、割り込み可能ロックなどである可能性があります。 、フィットネス愛好家、またはゲームプレーヤー。これは矛盾ではありません。OK、国際慣行、乾物。

0.1同期与ロック

  • Javaでロックする方法は2つあります。1つはsynchronizedキーワードを使用する方法で、もう1つはLockインターフェイスの実装クラスを使用する方法です。

したがって、パフォーマンスに関する特別な要件なしにロックを追加するだけの場合は、synchronizedキーワードを使用するだけで十分です。Java 5以降、java.util.concurrent.locksパッケージの下にロックを実装する別の方法があります。それはLockです。つまり、synchronizedはJava言語の組み込みキーワードであり、Lockはインターフェースです。このインターフェースの実装クラスは、コードレベルでロック関数を実装します。具体的な詳細については、この記事では説明しません。興味があれば、AbstractQueuedSynchronizerクラスを勉強して、書くことができます。素晴らしいと言いました。

実際、注意を払う必要があるのは、ReentrantLockクラス、ReadLockクラス、WriteLockクラスの3つのクラスだけです。

ReentrantLock、ReadLock、およびWriteLock は、Lockインターフェイスの3つの最も重要な実装クラスです。「再入可能ロック」、「読み取りロック」、「書き込みロック」に対応し、それらの使用については後で説明します。

ReadWriteLockは実際にはファクトリインターフェイスであり、ReentrantReadWriteLockはReadWriteLockの実装クラスであり、ReadLockとWriteLockの2つの静的内部クラスが含まれています。これらの2つの静的内部クラスは、それぞれLockインターフェイスを実装します。

使用の観点からのみ、ソースコードの詳細を調べるのをやめます。ロックと同期の違いは何ですか?次のいくつかのセクションでは、さまざまなロック分類の概念、および同期されたキーワードとさまざまなロック実装クラスの違いと接続について説明します。

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

  • リエントラントロック:ロックを取得した後、同じスレッドの外側の機能を参照し、スレッドの内側のメソッドを入力すると、自動的にロックが取得されます 前提,锁对象是同一个对象)は 、家の中のドアに似ています。入った後、トイレ、キッチンに入ることができます、など。

  • JavaのReentranLock(表示ロック)と同期(暗黙ロック)はどちらも再入可能ロックです。再入可能ロックの利点は、デッドロックをある程度回避できることです。 

  • 隐式锁:(synchronizedキーワードで使用されるロック)デフォルトは再入可能ロックです(同期ブロック、同期メソッド)

1.1 同期された暗黙のロック

  1. 各ロックオブジェクトには、ロックカウンターと、ロックを保持しているスレッドへのポインターがあります。
  2. monitorenterの実行時に、ターゲットロックオブジェクトのカウンタがゼロの場合、他のスレッドによって保持されていないことを意味します。Java仮想マシンは、ロックオブジェクトの保持スレッドを現在のスレッドとして設定し、そのカウンタを1つインクリメントします。 、それ以外の場合は待機する必要があります。保持スレッドがロックを解放するまで
  3. monitorexitが実行されると、ロックオブジェクトのカウンターがJava仮想マシンによってデクリメントされます。ゼロのカウンターは、ロックが解除されたことを意味します

å¨è¿éæå¥å¾çæè¿°

public class DemoSynchronized {

    Object object = new Object();

    public void sychronizedMethod() {
        new Thread(() -> {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "\t" + "外层....");
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "\t" + "中层....");
                    synchronized (object) {
                        System.out.println(Thread.currentThread().getName() + "\t" + "内层....");
                    }
                }
            }
        }, "A").start();
    }

    public static void main(String[] args) {
        new DemoSynchronized().sychronizedMethod();
        /**
         *  输出结果:
         *    A	外层....
         *    A	中层....
         *    A	内层....
         * */
    }
}

1.2  显示锁ReentrantLock

注:ロックと同じ数のロック解除があり、それらはペアで使用されます。1つ多いまたは1つ少ない場合、他のスレッドは待機状態になります。

public class DemoReentrantLock {
    static ReentrantLock reentrantLock=new ReentrantLock();

    public static void sendSms(){
        reentrantLock.lock();
        /*
        //reentrantLock.lock();
        注意有多少个lock,就有多少个unlock,他们是配对使用的
        如果多了一个lock(),那么会出现线程B一直处于等待状态
        * */
        reentrantLock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t"+"sendSms");
            sendEmails();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            reentrantLock.unlock();
        }
    }

    private static void sendEmails() {
        reentrantLock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t"+"sendEmails...");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) {
        DemoReentrantLock phone2=new DemoReentrantLock();
        new Thread(()->{phone2.sendSms();},"A").start();
        new Thread(()->{phone2.sendSms();},"B").start();
    }
}

2.悲観的ロックと楽観的ロック

ロックのマクロ分類は、悲観的ロック楽観的ロックです。ペシミスティックロックとオプティミスティックロックは、特定のロックを具体的に参照していません(JavaにはPessimisticLockまたはOptimisticLockと呼ばれるロック実装クラスはありません)が、並行状況での2つの異なる戦略を参照します。

  • ペシミスティックロック(ペシミスティックロック)は非常にペシミスティックです。データを取得するたびに、他の人がデータを変更すると思います。したがって、データを取得するたびに、データはロックされます。このようにして、ペシミスティックロックが解放されるまで、データを取得したい他のユーザーはブロックされます。
  • オプティミスティックロック(オプティミスティックロック)は非常に楽観的です。データを取得するたびに、他の人がデータを変更することはないと思います。だから、それはロックされません、それはロックされません!ただし、データを更新する場合は、更新前に、読み取りから更新までの間に他の誰かがデータを変更したかどうか確認します変更されている場合は、もう一度読み、更新を再試行し、更新が成功するまで上記の手順をループします(もちろん、更新に失敗したスレッドも操作を中止できます)。

ペシミスティックロックはトランザクションをブロックし、オプティミスティックロックはロールバックして再試行します。それぞれに長所と短所があります。一方が他方より優れているとは限りません。

  • 楽観的ロックは、書き込みが比較的少ない場合、つまり競合が実際にほとんど発生しない場合に適しています。これにより、ロックのオーバーヘッドを節約し、システムの全体的なスループットを向上させることができます。
  • ただし、競合が頻繁に発生する場合、上位レベルのアプリケーションは再試行を続行し、実際にはパフォーマンスが低下するため、この場合はペシミスティックロックを使用する方が適切です。

2.1楽観的ロック(CAS)の基礎

楽観的ロックと言えば、概念について言及する必要があります:CAS

CASとは何ですか?

比較交換、つまり、比較し、交換してください、また、セットの比較-と呼ばれる、比較および集合

  1. 比較:値Aが読み取られ、Bに更新する前に、元の値がまだAであるかどうかを確認します(他のスレッドによって変更されていない)。
  2. 設定:「はい」の場合、AをBに更新して終了します。[1]そうでない場合は、何もしません。

上記の2ステップの操作はアトミックであり、CPUの観点からは1ステップの操作である即時完了として簡単に理解できます。

CASを使用すると、楽観的ロックを実装できます

data = 123; // 共享数据

/* 更新数据的线程会进行如下操作 */
flag = true;
while (flag) {
    oldValue = data; // 保存原始数据
    newValue = doSomething(oldValue); 

    // 下面的部分为CAS操作,尝试更新data的值
    if (data == oldValue) { // 比较
        data = newValue; // 设置
        flag = false; // 结束
    } else {
	// 啥也不干,循环重试
    }
}
/* 
   很明显,这样的代码根本不是原子性的,
   因为真正的CAS利用了CPU指令,
   这里只是为了展示执行流程,本意是一样的。
*/

これは楽観的ロックのシンプルで直感的な実装であり、複数のスレッドが同時に読み取ることができます(ロック操作がまったくないため)が、1つのスレッドのみがデータを正常に更新し、他のスレッドがデータを更新することができます。ロールバックして再試行してください。CASはCPU命令を使用して、ハードウェアレベルからの操作のアトミック性を確保し、ロックのような効果を実現します。

プロセス全体に「ロック」および「ロック解除」操作がないため、楽観的ロック戦略はロックフリープログラミングとも呼ばれます言い換えれば、楽観的ロックは実際には「ロック」ではなく、ループでCASを再試行するアルゴリズムにすぎません。

実際、楽観的ロックCASはCPUリソースを消費しますが、オンラインテキストの切り替えも回避します。

2.2悲観的ロックと楽観的ロックの詳細な説明

Javaで使用するほとんどすべてのロックは悲観的なロックです。バイアスロック、軽量ロックから重量ロック、すべて悲観的なロックに同期されます。JDKによって提供されるLock実装クラスは、すべて悲観的なロックです。実際、「ロックオブジェクト」がある限り、それは悲観的なロックでなければなりません。ので楽観的ロックは、ロックが、ループ内でCASをしようとするアルゴリズムではありません。

  • JDK同時実行パッケージに楽観的ロックはありますか?

持ってる。java.util.concurrent.atomicパッケージのアトミッククラスはすべて、楽観的ロックを使用して実装されます。

  • アトミッククラスAtomicIntegerの自動インクリメントメソッドは楽観的ロック戦略です

  • インターネット上の一部の情報が、バイアスロックと軽量ロックが楽観的なロックであると信じているのはなぜですか

その理由は、彼らが下部でCASを使用しているからですか?それとも、「楽観的/悲観的」と「軽量/軽量」を混同しているのでしょうか。実際、スレッドがこれらのロックをプリエンプトする場合、それは確かにループ+ CAS操作であり、楽観的ロックのように感じられます。しかし、問題の核心は、ロックが悲観的ロックであるか楽観的ロックであるかを言うとき、プロセスを確認するために最下部に立つのではなく、常にアプリケーション層に立ってアプリケーションデータをロックする方法を確認する必要があることです。ロックをプリエンプトします。スレッドがロックを取得しようとして、すでに占有されていることがわかった場合、スレッドは引き続きデータを読み取り、後で更新する必要があるときに再試行するかどうかを決定しますか?バイアスロックと軽量ロックの場合、答えは明らかにノーです。一時停止中かビジー状態かに関係なく、アプリケーションデータの読み取り操作は「ブロック」されます。この観点から、それらは確かに悲観的なロックです。

関連記事

  1. ここで問題があります。つまり、値がAからBに変化し、次にBからAに戻ります。この場合、CASは値が変更されていないと見なす場合がありますが、実際には変更されています。この点で、並行パッケージの下にAtomicStampedReferenceがあり、バージョン番号に基づいて実装を提供します。
  2. Java割り込みメカニズム:https:  //www.cnblogs.com/jiangzhaowei/p/7209949.html

3.スピンロック

スピンロックと呼ばれるタイプのロックがありますいわゆるスピンは、率直に言って、while(true)無限ループです。

  • スピンロック:スピンロックは、ロックを取得するための無限ループです。これは、値を設定するための楽観的ロックの無限ループとは異なります。

楽観的ロックはちょうど今同様の無限ループ操作を持っているので、それはスピンロックですか?

そうではありません。spinとwhile(true)の操作は同じですが、2つの項を分離する必要があります。「スピン」という言葉は、具体的にはスピンロックのスピンを指します。

ただし、JDKにはスピンロック(SpinLock)がないので、スピンロックとは何ですか?次のセクションを読んだ後でわかります。

  • スピンロックは、ロックを取得するための無限ループです。この違いは、値を設定するための楽観的ロックの無限ループとは異なります。

4.同期ロックのアップグレード:バイアスロック→軽量ロック→重量ロック

ロックエスカレーションから同期されてロックされないアップグレードはバイアスされたロックであり軽量ロックにアップグレードされ、最後に重量ロックにアップグレードされます。スピンロックはどこにありますか?こちらの軽量ロックはスピンロックです。

      

同期コードブロックが初めて実行されると、ロックオブジェクトはバイアスロックになります(オブジェクトヘッダーのロックフラグはCASを介して変更されます)。これは、文字通り「最初のスレッドにバイアスされて取得する」ロックを意味します。同期コードブロックを実行した後、スレッドはバイアスロックをアクティブに解放しませ同期コードブロックに2回目に到達すると、スレッドはロックを保持しているスレッドがそれ自体であるかどうかを判断し(ロックを保持しているスレッドIDもオブジェクトヘッダーにあります)、そうである場合は正常に実行されます。ロックは以前に解放されていないため、ここでロックを再度有効にする必要はありません。最初から最後までロックを使用するスレッドが1つしかない場合、ロックに有利な追加のオーバーヘッドがほとんどなく、パフォーマンスが非常に高いことは明らかです。

2番目のスレッドがロック競争参加すると、バイアスロックは軽量ロック(スピンロック)にアップグレードされます。ここで、ロック競合とは何かを明確にする必要があります。複数のスレッドが順番にロックを取得するが、ロックが取得されるたびにスムーズに進み、ブロッキングが発生しない場合、ロック競合は発生しません。スレッドがロックを取得しようとして、ロックがすでに占有されていることを検出し、ロックが解放されるのを待つことしかできない場合にのみ、ロックの競合が発生します。

軽量ロック状態では、ロックの競合が続き、ロックを取得していないスレッドが回転します。つまり、ロックを正常に取得できるかどうかを判断するためにループを続けます。ロックを取得する操作は、実際にはCASを介してオブジェクトヘッダーのロックフラグを変更することです。まず比較現在のロックフラグを「解放」されているかどうか、そしてそれがあれば、それを設定する「ロック」する。比較や設定が起こるアトミックこれはロックを取得したと見なされ、スレッドは現在のロックホルダー情報をそれ自体に変更します。

長期間のスピン操作は非常にリソースを消費します。スレッドがロックを保持している場合、他のスレッドはその場でCPUを消費するだけで、効果的なタスクを実行できません。この現象はビジー待機と呼ばれます。複数のスレッドがロックを使用しているが、ロックの競合が発生していない場合、またはごくわずかなロックの競合が発生している場合、同期では軽量ロックを使用して短期間のビジー状態を許容します。これは、ユーザーモードとカーネルモード間のスレッド切り替えのオーバーヘッドと引き換えに、妥協案や短期間のビジー状態などです。

明らかに、このビジー待機は制限されています(スピン数を記録するためのカウンターがあり、デフォルトで10サイクルが許可されていますが、これは仮想マシンのパラメーターによって変更できます)。ロックの競合が深刻な場合、最大スピン数に達したスレッドは、軽量ロックを重量ロックにアップグレードします(CASはロックフラグを変更しますが、ロックを保持するスレッドIDは変更しません)。後続のスレッドがロックを取得しようとして、占有されているロックがヘビーウェイトロックであることが判明すると、(ビジー待機ではなく)直接一時停止し、将来起動されるのを待ちます。JDK1.6より前は、

Synchronizedは、重量のあるロックを直接追加します。明らかに、今では十分に最適化されています。

ロックは、バイアスロック、軽量ロック、および重量ロック(ロック拡張とも呼ばれますの順序でのみ段階的にアップグレードでき、ダウングレードは許可されていません。

バイアスされたロックの機能は、ロックを保持しているスレッドが、同期されたコードブロックの実行を終了したときにロックを解放しないことです。したがって、2番目のスレッドがこの同期されたコードブロックに対して実行されると、ロックの競合が発生してから、軽量ロックにアップグレードされますか?
スレッドAが同期コードブロックを初めて実行した後、スレッドBがロックを取得しようとすると、バイアスされたロックであることが検出され、スレッドAがまだ生きているかどうかが判断されます。スレッドAがまだ生きている場合、スレッドAは一時停止されます。このとき、バイアスロックは軽量ロックにアップグレードされます。その後、スレッドAは実行を継続し、スレッドBが回転します。ただし、スレッドAが存在しないと判断された場合、スレッドBはこのバイアスされたロックを保持し、ロックはアップグレードされません。
これについて疑問を持っている人はまだいます。以前は明確に説明していませんでしたが、新しい概念が多すぎて拡張できない場合は、新しい記事を開くことができます。さらに、低レベルであることがいくつかあり、ソースコードを読んでおらず、自分が正しいはずだと確信していません。実際、軽量ロックにアップグレードする前に、仮想マシンはスレッドAが安全な場所でできるだけ早く中断し、スタック内の情報を「偽造」して、スレッドAが光を保持していると見なすようにします。目覚めた後ウェイトロック スレッドAが以前に同期されたコードブロックにあった場合、スレッドBはスピンして待機できます。スレッドAが以前に同期されたコードブロックになかった場合、スレッドAは起動後にこの状況をチェックし、スレッドBが取得できるようにすぐにロックを解除します。私はこれまでコンテンツのこの部分を深く研究したことがありません。何か問題がある場合は、アドバイスしてください。

5.フェアロックとアンフェアロック

複数のスレッドがフェアロックを適用する場合、ロックが解放されると、最初に適用されたスレッドが最初のスレッドを取得します。これは非常にフェアです。明らかに、それが不公平なロックである場合、アプリケーションの後のスレッドは、ランダムに、または他の優先順位に従って、最初にロックを取得する可能性があります。

ReentrantLockクラスの場合、コンストラクター介してパラメーターを渡すことで、ロックが公正なロックであるかどうかを指定でき、デフォルトは不公正なロックです。一般に、不公平なロックのスループットは公平なロックのスループットよりも大きく、特別な要件がない場合は、不公平なロックが優先されます。

  • ReentrantLockコンストラクターは、fairまたはunfairとして指定できます
  • 同期の場合、これも不公平なロックですが、公平なロックにする方法はありません。

6.割り込み可能なロック

割り込み可能なロックは、文字通り「割り込みに応答できるロック」を意味します。

ここで重要なのは、割り込みとは何かを理解することですJavaは、スレッドに直接割り込みをかけるメソッドを提供せず、割り込みメカニズムのみを提供します「割り込みメカニズム」とは?スレッドAは「実行を停止してください」要求をスレッドBに送信します(スレッドBはこの要求をそれ自体に送信することもできます)が、スレッドBはすぐに実行を停止しませんが、独自の方法で割り込みに応答する適切なタイミングを選択します。この割り込みを直接無視することもできます。つまり、Javaの中断によってスレッドを直接終了することはできませんが、中断されたスレッドはその処理方法を決定する必要があります。これは、親が子供たちに体に注意を払うように言っているようなものですが、子供たちが体に注意を払うかどうか、または体に注意を払う方法は完全に彼ら次第です。[2]

ロックのトピックに戻ると、スレッドAがロックを保持している場合、スレッドBはロックの取得を待機します。スレッドAがロックを保持している時間が長すぎるため、スレッドBはもう待ちたくありません。スレッドBに割り込みを許可するか、他のスレッドで割り込みをかけることができます。これは割り込み可能なロックです。

Javaでは、synchronizedは中断不可能なロックであり、Lockの実装クラスはすべて中断可能なロックです。Lockインターフェイスを確認するだけです。

/* Lock接口 */
public interface Lock {

    void lock(); // 拿不到锁就一直等,拿到马上返回。

    // 拿不到锁就一直等,如果等待时收到中断请求,则需要处理InterruptedException。
    void lockInterruptibly() throws InterruptedException;  

    // 无论拿不拿得到锁,都马上返回。拿到返回true,拿不到返回false。
    boolean tryLock(); 

    // 同上,可以自定义等待的时间。
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
}

7.読み取り/書き込みロック、共有ロック、ミューテックスロック

読み取り/書き込みロックは、実際には、読み取りロック(共有ロック)と書き込みロック(相互排他ロック、排他ロック)のペアです。

JavaのReadWriteLockインターフェースを見てください。これは2つのメソッドのみを指定します。1つは読み取りロックを返し、もう1つは書き込みロックを返します。

以前の楽観的ロック戦略を覚えていますか?すべてのスレッドはいつでも読み取ることができ、書き込む前に値が変更されたかどうかのみを判断します。

読み取り/書き込みロックは実際には同じことを行いますが、戦略は少し異なります。多くの場合、スレッドはデータの読み取り後に更新するかどうかを認識しています。では、ロックするときにこれを直接明確にしてみませんか?値を読み取って更新する場合(SQL for updateの意味)、ロックロックすると、書き込みロックが直接追加されます書き込みロックを保持すると、他のスレッドは読み取りと書き込みの両方を待機する必要があります。読み取りデータがフロントエンド表示専用の場合、ロックがロックされると読み取りロックが明示的に追加されます。他のスレッドも読み取りロックを追加する必要がある場合、待機せずに直接取得できます(読み取りロックカウンター+ 1 )。

読み取り/書き込みロックは楽観的なロックに少し似ていますが、読み取り/書き込みロックは悲観的なロック戦略です。読み取り/書き込みロックは更新前に値が変更されたかどうかを判断するのではなく、ロックする前に読み取りロックと書き込みロックのどちらを使用するかを決定するためです。楽観的ロックとは、具体的にはロックフリープログラミングを指します。それでも疑問がある場合は、最初と2番目のセクションに戻って、「楽観的ロック」とは何かを確認できます。

JDKによって提供されるReadWriteLockインターフェースの唯一の実装クラスはReentrantReadWriteLockです。名前を見るだけで、読み取り/書き込みロックだけでなく、再入可能ロックも提供されます。2つのインターフェイスメソッドに加えて、ReentrantReadWriteLockは、外部が内部の動作ステータスを監視するのを容易にするいくつかのメソッドも提供しますが、ここでは展開しません。

 

 

 

おすすめ

転載: blog.csdn.net/qq_41893274/article/details/113801002