バイアスロック理論は抽象的すぎます。実際には、バイアスロックがどのように発生し、どのようにアップグレードするかを理解してください[実際の戦闘]

一緒に書く習慣を身につけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して3日目です。クリックしてイベントの詳細をご覧ください。

ロックのアップグレード

  • 上記では、主にバイアスロック、軽量ロック、および重量ロックとは何かを紹介します。そして、3つの違いと使用シナリオを分析します。Redisの章の整数セットのアップグレード操作を覚えておいてください。ロックでは、ロックの昇格と降格も設計します。上記では、競争がない場合はバイアスロックを導入し、競争がある場合は軽量ロックも導入しました。
  • しかし、軽量ロックはcas操作とスピン待機です。スピンは同時実行性の低い状況にのみ適しています。同時スレッドが多い場合、ロックの取得に時間がかかる可能性があり、スピン中のオーバーヘッドも大きいため、軽量ロックをアップグレードする必要があります。では、重量級ロックをアップグレードする時期はいつですか?スピン数もJVMで設定されており、一定回数を超えるとヘビーウェイトロックにアップグレードされます。

バイアスロックアップグレード軽量ロック

  • 個人的には、まだロックエスカレーションのプロセスに焦点が当てられていると思います。バイアスされたロックはアクティブに取り消されないため、ロックエスカレーションプロセスには、バッチロックの取り消しやバッチロックバイアスなどのシナリオが含まれます。

画像-20211213152554303.png

  • ロックオブジェクトのマークワードにあるバイアスロックのストレージ構造を思い出してください。最後の3桁は、バイアスロックを示す101です。ロックレコードについては、上記のスレッドスタックの最上位にあるロックレコードオブジェクトのポインタです。ロックレコードについては、ロックオブジェクト全体のマークワードが内部に格納されているため、ここではEPOCHに注意する必要があります。エポックの意味。バージョンが良いことを理解しているだけです
  • バージョン番号について言えば、バイアスロックに関するJVMの2つのプロパティ設定にも精通している必要があります。

画像-20211213153045152.png

  • バイアスロックの取り消しは、軽量ロックのエスカレーションが発生したときに発生します。JVMは、特定のタイプのロックのロック取り消しの数が以上である-XX:BiasedLockIngBulkRebiasThreshold=20ことを検出すると、バイアスされたロックが無効であると宣言します。バイアスロックを無効にするには、バージョン番号に1を追加します。つまり、EPOCH+1です。
  • クラスロックの失効の総数が、以上の場合-XX:BiasedLockingBulkRevokeThreshold=40、後続のロックはデフォルトで軽量ロックになります。
class Demo{
    String userName;
}
public class LockRevoke {
    public static void main(String[] args) throws InterruptedException {
        List<Demo> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            list.add(new Demo());
        }
        final Thread t1 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                for (int i = 0; i < 99; i++) {
                    Demo demo = list.get(i);
                    synchronized (demo) {
                    }
                }
                TimeUnit.SECONDS.sleep(100000);
            }
        });

        final Thread t2 = new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                synchronized (list.get(99)) {
                    System.out.println("第100个对象上锁中,并持续使用该对象" + ClassLayout.parseInstance(list.get(99)).toPrintable());
                    TimeUnit.SECONDS.sleep(99999);
                }
            }
        });

        final Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 40; i++) {
                    Demo demo = list.get(i);
                    synchronized (demo) {


                        if (i == 18) {
                            System.out.println("发送第19次锁升级,list.get(18)应该是轻量级锁" + ClassLayout.parseInstance(list.get(18)).toPrintable());
                        }
                        if (i == 19) {
                            System.out.println("发送第20次锁升级,会发生批量重偏向;纪元+1;后续偏向锁都会偏向当前线程;list.get(19)应该是轻量级锁" + ClassLayout.parseInstance(list.get(19)).toPrintable());
                            System.out.println("因为第100对象仍然在使用,需要修改起纪元" + ClassLayout.parseInstance(list.get(99)).toPrintable());
                        }
                        if (i == 29) {
                            System.out.println("在批量重偏向之后;因为第一次偏向锁已经失效了,所以这里不是轻量级而是偏向该线程的偏向锁" + ClassLayout.parseInstance(list.get(29)).toPrintable());
                        }
                        if (i == 39) {
                            System.out.println("发送第40次锁升级,发生批量锁撤销;这里应该是轻量级锁后续都是轻量级" + ClassLayout.parseInstance(list.get(39)).toPrintable());
                        }
                    }
                }

            }
        });
        
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(5);
        System.out.println("第一次上锁后list.get(0)应该偏向锁:" + ClassLayout.parseInstance(list.get(0)).toPrintable());
        System.out.println("第一次上锁后list.get(19)应该偏向锁:" + ClassLayout.parseInstance(list.get(19)).toPrintable());
        System.out.println("第一次上锁后list.get(29)应该偏向锁:" + ClassLayout.parseInstance(list.get(29)).toPrintable());
        System.out.println("第一次上锁后list.get(39)应该偏向锁:" + ClassLayout.parseInstance(list.get(39)).toPrintable());
        System.out.println("第一次上锁后list.get(99)应该偏向锁:" + ClassLayout.parseInstance(list.get(99)).toPrintable());
        t3.start();
       

    }

}
复制代码
  • 上記は、バイアスされたロックの重いバイアスとバイアスされたロックの取り消しの典型的なケースの統合です。
  • まず、最初の99個のオブジェクトをロックしてすぐに解放するのはt1スレッドです。これは、バイアスロックをキャンセルするために仮想マシンの設定が遅れているためです。設定方法については、記事の冒頭を参照してください。
  • 2番目のスレッドt2は最後のオブジェクトのみをロックします。違いは、オブジェクトが永続的に占有され、ロックされた後に解放されないことです。そうすると、他の人は最後のオブジェクトのロックを取得できなくなります
  • 3番目のスレッドは、上記で初期化されたオブジェクトを使用してリソースのプリエンプトを開始します。JVMによるバイアスされたロックを取り消すデフォルトの最大数は40回であるため、3番目のスレッドは40回しかループしません。後ろに軽量のロックがあります。
  • 3番目のスレッドはバッチで再バイアスされるため、その後のバイアスされたロックの取り消しは発生しません。バッチロックの取り消しが表示された場合は、ロックするスレッドを開く必要があります。したがって、スレッド4は引き続きキャンセルを引き起こしますが、スレッド4の後に実行する必要があります。そうしないと、t3とt4が同時に実行され、ヘビーウェイトロックのシナリオの1つが次のようになるため、ヘビーウェイトロックが発生します。 1つの軽量ロック、1つはヘビーウェイトロックをトリガーするように要求しています
  • i == 18、つまり19番目の要素が3番目のスレッドでロックされている場合、バイアスロックは以前にロックされているため、ロックは解除されますが、バイアスロック自体は解除されません。したがって、この時点では、19番目の要素で最初にロックが取り消され、次に軽量ロックが適用されます。したがって、19番目のオブジェクトを予測するときの軽量ロックがあります
  • 次に、20番目の要素であるi == 19に到達します。これは、JVMのデフォルトのクラスの合計失効が20以上になると、バッチの再バイアスが発生するためです。どう言う意味ですか?t3でi==19の前にロックするのは軽量です。i == 19の後、ロックされるとロックはバイアスされますが、スレッド1ではなくスレッド3にバイアスされます。ここでは、最初のi == 19メモリレイアウトと比較できますが、スレッドIDが異なり、エポックも異なります。

画像-20211213161634774.png

  • 上記のlist.get(99)をロックするために別のスレッドを開始するのはなぜですか?バッチの再バイアスが発生したときに、ロックが破棄されないように、使用中のロックエポック情報が変更されていることを直感的に確認できることをテストします。

画像-20211213162536276.png

  • バッチ再バイアスが発生すると、使用中のロックエポック情報が更新されることがわかります。更新されていない場合、JVMはそれを放棄されたバイアスロックと見なします。もちろん、バッチの再バイアスが発生した後、オブジェクトロックが再度取得されても、オブジェクトロックは取り消されません。前のロックが破棄されたので、次のロック情報を取得しましょう。list.get(29)を見てみましょう。

画像-20211213162904537.png

  • 4番目のスレッドは3番目のスレッドの後も失効を引き起こし続け、失効の総数が40に達すると、JVMは、このタイプの後続のロックはバイアスされたロックには適さず、直接軽量のロックであると見なします。

画像-20211213164055601.png

おすすめ

転載: juejin.im/post/7087740752398139399