Javaの基本: CASの詳細な説明


質問: 1. CAS を知っていますか? それはどのように達成されますか?
2. 投票数など共有整数変数 n がある場合、売却のたびに 1 ずつ減らす必要がありますが、n- を使用しても問題ありませんか? 問題はありますか?

回答:
CAS の正式名称は Compare and swap で、 これは CPU 同時実行プリミティブです

その機能は、メモリ内のある場所の値が期待値であるかどうかを判断し、そうであれば最新の値に変更することです.このプロセスはアトミックです.

JAVA 言語で具現化された CAS 同時実行プリミティブは、sun.misc.Unsafe クラスのさまざまなメソッドです。Unsafe クラスで CAS メソッドを呼び出すと、JVM が CAS アセンブリ命令の実装に役立ちますこれは、アトミック操作を実現する完全にハードウェアに依存する機能です。

たとえば、CAS の関数の 1 つは、前回のブログ記事で説明した volatile がアトミック性を保証しないという問題に対処でき、マルチスレッド環境で単一の共有変数の n++ の問題を解決できます。

CAS の役割を理解したい場合は、まず volatile の役割を完全に理解する必要があります. よくわからない場合は、まずこのブログ投稿 ( Java Basics: Detailed Explanation of volatile ) を読んでから、CAS を理解してください。一部の概念はこの記事では繰り返されないため、理解できない場合があります。

1. CASの詳しい説明

前述のように volatile ではアトミック性が保証されないため、マルチスレッドで n++ 操作を行うと問題が発生します. 解決策の 1 つは AtomicInteger クラスを使用して処理を行うことです. 次に、AtomicInteger クラスを使用することでこの問題を解決できる理由を分析します.

1.1 AtomicInteger クラスの compareAndSet メソッドを理解する

まず、AtomicInteger クラスの compareAndSet メソッドを見てください。チケットの購入を例にとると、最初のチケット数は 10 で、スレッド 1 が最初にチケットを購入し、スレッド 2 が後でチケットを購入すると仮定すると、2 つのスレッドが同時にチケットを購入します。

実行結果を下図に示します。スレッド 2 が false を返していることがわかります。つまり、チケットは正常に購入されませんでした。

package com.koping.test;

import java.util.concurrent.atomic.AtomicInteger;

public class CasDemo {
    
    
    public static void main(String[] args) {
    
    
        AtomicInteger atomicInteger = new AtomicInteger(10);

        // 业务逻辑:n行代码

        // 线程1,抢到票就将票数减1
        System.out.println(atomicInteger.compareAndSet(10, 9) + "\t 现在票数为: " + atomicInteger.get());

        // 线程2,抢到票就将票数减1
        System.out.println(atomicInteger.compareAndSet(10, 9) + "\t 现在票数为: " + atomicInteger.get());
    }
}

ここに画像の説明を挿入

上記の例と、以下の compareAndSet メソッドの呼び出し関係を組み合わせると、よく理解できます。このメソッドは、オブジェクトの現在の値が expectedValue? と等しいかどうかを判断するためのものです。
等しい場合は、現在のオブジェクトの値を newValue に設定して true を返し、
等しくない場合は false を返します。
ここに画像の説明を挿入
ここに画像の説明を挿入

1.2 AtomicInteger クラスの getAndIncrement メソッドを理解する

次に、AtomicInteger クラスの getAndIncrement メソッドを使用すると、マルチスレッドの状況で n++ を置き換えることができ、原子性の問題が発生しない理由を分析してみましょう。

getAndIncrement メソッドのソース コードを見ることができます。
1. 183 行で、Unsafe クラスの getAndAddInt メソッドが呼び出されます: U.getAndAddInt(this, VALUE, 1);
  1.1 unsafe クラスの getAndAddInt メソッドでは、まず、現在のメイン メモリ上の最新の
    値を現在のオブジェクトとオフセットを指定してから、compareAndSetInt メソッドを呼び出して、値が正常に変更されたかどうかを判断します。
    1 の加算に成功するとループを抜け、
    失敗すると再びループに入り、メインメモリの最新値を再取得して再度判定します。
ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入

この時点で、最初に Unsafe クラスとは何かを完全に理解することが非常に重要です!

1. Unsafe クラスは CAS のコア クラスです.Java メソッドは下層のシステムに直接アクセスできないため、ローカル (ネイティブ) メソッドを介してアクセスする必要があります。しかし、Unsafe クラスは特定のメモリ内のデータを直接操作できます

Unsafe クラスは sun.misc パッケージに存在し、このクラスのすべてのメソッドはネイティブに変更されています。つまり、Unsafe クラスのメソッドは、オペレーティング システムの基礎となるリソースを直接呼び出して、対応するタスクを実行します

2. getAndAddInt メソッドのオフセットは、メモリ内の変数値のオフセット アドレスを示し、Unsafe クラスはメモリ オフセット アドレスに従ってデータを取得します。
3. AtomicInteger クラスの値は volatile で変更されるため、複数のスレッド間の可視性が保証されます。
ここに画像の説明を挿入
4. CAS の正式名称は Compare and swap で、CPU 同時実行プリミティブです
その機能は、メモリ内のある場所の値が期待値であるかどうかを判断し、そうであれば最新の値に変更することです.このプロセスはアトミックです.

JAVA 言語で具現化された CAS 同時実行プリミティブは、sun.misc.Unsafe クラスのさまざまなメソッドです。Unsafe クラスで CAS メソッドを呼び出すと、JVM が CAS アセンブリ命令の実装を支援します。これは、アトミック操作を実現する完全にハードウェアに依存する機能です。

CAS はシステム プリミティブであるため、プリミティブはオペレーティング システムのカテゴリに属し、いくつかの命令で構成されています.これは、ユーザーが特定の機能を完了するためのプロセスであり、プリミティブの実行は連続的でなければなりません.中断しました。したがって、CAS は CPU のアトミック命令であり、いわゆるデータ不一致の問題は発生しません

1.3 n++ の代わりに AtomicInteger クラスを使用することの分析

1.2 の AtomicInteger クラスの getAndIncrement メソッドの分析と組み合わせると、マルチスレッドの場合に n++ の代わりに AtomicInteger クラスの getAndIncrement メソッドを使用できる理由が簡単に理解できるはずです。

getAndIncrement メソッドは Unsafe クラスの CAS メソッドを呼び出しますが、このメソッドは割り込みを受けない CPU アトミック命令であるため、マルチスレッドの場合は n++ 以降の結果が正しいことが保証されます。したがって、volatile が原子性を保証しないという問題は解決できます。

同時に、getAndAddInt メソッドを見ると、最初にループ内のメイン メモリ内のオブジェクトの最新の値を取得し、次に CAS を使用して置換された値が以前と同じであることを確認していることがわかります。つまり、つまり、現在のスレッドの前にメイン メモリに置き換えられて書き戻されます。

失敗した場合は、他のスレッドがオブジェクトの値を変更したことを意味するため、オブジェクトを再取得してからメイン メモリに書き戻す必要があります。

ここに画像の説明を挿入
ずお

2. CAS の短所

CAS には主に 3 つの欠点があります。1
) 長いサイクル時間と高いオーバーヘッド。おそらく、スレッド 1 は、CAS 操作を実行するたびに値が変更されていることを検出し、ループを維持します。
2) 共有変数のアトミック操作は 1 つだけ保証されます。複数のシェア変数操作がある場合、最終的に同期が使用されます。
3) ABA 問題。
たとえば、オブジェクトの初期値は 5 です
1) スレッド 1 が値を 6 に変更しようとしています
2) このとき、スレッド 2 が CPU を占有して値を 8 に変更しました
3) その後、スレッド 3 が CPU を占有して値を変更しました
この時点で、スレッド 1 は最終的に CPU を占有し、CAS を使用して元の値と現在の値が両方とも 6 であることを確認し、操作を実行できるようになりましたしかし実際には、このプロセスでは他のスレッドが操作を実行しており、別の論理例外が発生している可能性があり、これが ABA 問題です。

おすすめ

転載: blog.csdn.net/xueping_wu/article/details/124571158