Javaのマルチスレッドのインテルのマニュアル、CASの基本原理は、私はソースコードとJavaからその後、OpenJDKのソースのアセンブリコードを参照してくださいどのように勉強する(3)学習

私が読んで準備ができたかもしれないj.u.c次のパケットConcurrentHashMap、ソースコードの基礎となるを理解しConcurrentHashMap、実装の原理を、その使用にCASの多くを見つけるために、少し読んで。そして、atomicそしてlocksこの2つのパッケージはそうそしてちょうどCASの最初の原則の後に把握する上で動く、またCASの広範な使用です。

私は多くの記事を読んで、だけでなく、それのこつ。私は彼がJavaソースコードとアセンブリコードのOpenJDKからソースコードを見て驚いたと思いますし、最終的にインテルのマニュアルの一部の内容を読んで、そして最終的には唯一のCASに学んでいないが、他の多くの知識を学んだしませんでしたので。

ゆっくりと、実際には、中に綿密な研究は、特定の知識を得るため、または非常に興味深いが、プロセスは少し難しいかもしれする、ことを発見。


A、CAS導入しました

CASは、ロックが軽量であり、楽観的ロックの実装である、j.u.c多くのツールを達成CASに基づいています。

1. CASは何ですか?

CAS(コンペア・アンド・スワップ)の比較とスワップ操作。

CASは、3つのオペランド、即ちメモリ位置V、古いの期待値の新しい値を有し、B. A修飾提案します もしと期待値A、新しい値Bと更新された値Vに沿ってVが、そうでない場合は何もしない場合に限ります。

ヘルプにはいくつかの擬似コードでCASを理解します:

Object  A = getValueFromV();// 先读取内存位置 V 处的值 A
Object B = A + balaba;// 对 Value 做一定处理,得到新值 B
	
// 下面这部分就是 CAS,通过硬件指令实现
if( A == actualValueAtV ) {// actualValueAtV 为执行当前原子操作时内存位置 V 处的值
	setNewValueToV(B);// 将新值 B 更新到内存位置 V 处
} else {
	doNothing();// 说明有其他线程改过内存位置 V 处的值了,A 已经不是最新值了,所以基于 A 处理得到的新值 B 是不对的
}
复制代码

2、CAS利点

並行性競合のレベルでロック解除は、非常に高い効率高くありません。(楽観的ロックの利点を参照してください)

3、CASは何の欠陥?

引き起こす可能性(1)CAS +スピン==>:長いサイクル時間、大きいCPUオーバーヘッド

ほとんどの場合、CASは、単一の共有変数を達成するためにスピンで更新されます。

(大記述同時コンフリクト)長時間スピンCASが失敗した場合、それは非常に大きなCPUの実行コストをもたらすでしょう。

(2)ABA問題

まず、ということを理解:CAS自体はアトミックな操作で、ABAは問題ではありません。

しかし、CASはデータを更新するために使用される一般的に3つの手順が必要です。

  • 取数
  • 情報処理
  • CASの更新データ

プロセスで発生する可能性のあるABAの問題。上記の3つのステップは、アトミック操作ではない、以下のことが発生する可能性があります。

  • 値a、データ開始の処理スレッド1クエリスレッド
  • Bの3つのステップの更新された値Aを実行するスレッド2スレッド
  • B背面及び更新AからAの値を3つのステップを実行Thread3スレッド
  • 処理されたデータ、得られたC、スレッド1のスレッドが、それはメモリ比較は、A、Cの値を更新値

データ処理におけるスレッド1のスレッドは、実際には、Aの値が施されたa -> b -> aプロセスには、スレッド1のスレッドのために決定されるか、スレッド1のスレッドがA cの値を更新してもよいです。これは、私たちがABAの問題を呼んでいます。

ABAの問題についてシミュレートするためのJavaコード:

/**
 * @author HappyFeet
 * @since Jan 02, 2020
 */

public class CasABAProblem {

    private static final Unsafe unsafe = UnsafeUtils.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                    (CasABAProblem.class.getDeclaredField("value"));
        } catch (Exception ex) {
            throw new Error(ex);
        }
    }

    private volatile int value = 0;

    public static void main(String[] args) throws InterruptedException {

        CasABAProblem abaProblem = new CasABAProblem();

        Thread thread1 = new Thread(() -> {
            int millis = 1000;
            int value = abaProblem.getValue();
            println("value is {}, and sleep {} millis.", value, millis);
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                // do nothing
            }
            boolean cas = unsafe.compareAndSwapInt(abaProblem, valueOffset, value, value + 50);
            println("cas is {}, value from {} to {} after CAS.", cas, value, abaProblem.getValue());
        }, "thread1");

        Thread thread2 = new Thread(() -> {
            int millis = 200;
            println("sleep {} millis", millis);
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                // do nothing
            }

            int value = abaProblem.getValue();
            boolean cas = unsafe.compareAndSwapInt(abaProblem, valueOffset, value, value - 100);
            println("cas is {}, value from {} to {} after CAS.", cas, value, abaProblem.getValue());
        }, "thread2");

        Thread thread3 = new Thread(() -> {
            int value = abaProblem.getValue();
            boolean cas = unsafe.compareAndSwapInt(abaProblem, valueOffset, value, value + 100);
            println("cas is {}, value from {} to {} after CAS.", cas, value, abaProblem.getValue());
        }, "thread3");

        thread1.start();
        thread2.start();
        thread3.start();

        thread1.join();

    }

    public int getValue() {
        return value;
    }
}
复制代码

次のようにプログラムの実行、結果は以下のとおりです。

2020-01-02T22:57:48.565 thread thread2 : sleep 200 millis
2020-01-02T22:57:48.565 thread thread1 : value is 0, and sleep 1000 millis.
2020-01-02T22:57:48.566 thread thread3 : cas is true, value from 0 to 100 after CAS.
2020-01-02T22:57:48.783 thread thread2 : cas is true, value from 100 to 0 after CAS.
2020-01-02T22:57:49.585 thread thread1 : cas is true, value from 0 to 50 after CAS.
复制代码

それは何を発行ABAの潜在的な問題でしょうか?言い換えれば、a -> b -> a副作用を有することができ、プロセス?

長い時間のために考えて、私は何でも良い例を期待していませんでした。そして、私たちは、更新後に再度思いました。ここでは、ABAの問題を回避する方法を見て。

実際には、ABAの問題を回避するためには、単にバージョン番号を追加するためにデータを与える、非常に簡単です。上記の例a -> b -> aのプロセスは、となるであろう1a -> 2b -> 3a処理されたデータスレッド1のスレッドは、ことがわかった場合に、1a != 3aAの値は、それが更新されないように。

私も試してみましたcompareAndSwapObject、私は、オブジェクトの属性を変更する方法に関係なくので、比較のこのメソッドは、オブジェクトへの参照で見つかった、方法をcompareAndSwapObject正常に実行することができます。だから、安全でない中compareAndSwapcompare使用できるかどうか==同等のものを行うには?私は見て、それは本当にそう書かれているかのように、ドキュメント:AtomicReferencecompareAndSet(V expect, V update)

* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
复制代码

私は多くの人がABAの問題上記のブログは、彼は関連リンクリストまたはスタックスタックスタックの例を挙げて書くご覧ください。ウィキペディア上記の例を参照:比較およびスワップ

スタックのスタック操作ポップ及びプッシュ一連の動作であるC。そして説明:原因広く使われているメモリの再利用機構のメモリ管理機構に、前のアドレスノードCと一致ノードAにつながります。

これは、Java DOになりますか?私はそれが思考を考えることができると思います。

(3)は、複数の共有変数アトミック操作をサポートしていません

ビューの上記の説明から、複数の共有変数の単一の共有変数のCAS自体は、当然のことながら、サポートされていません。

共有変数の複数の共有変数(オブジェクト配置内部)に結合されている場合もちろん、CAS動作を行ってもよいです。

これを理解する方法を見多个共享变量て、共有変数のプロパティを呼び出すことができるならば多个共享变量、その後、CASもサポートすることができます。

第二に、CASの実装の基本原理

CASは、安全でないクラスの中核です。通話中に発見安全でないソースコードを見ると、それはあるnative方法。しかしによって異なりnative、我々は多くに多くの労力を費やす必要があり、基本的にはC ++のコードを実行し、メソッドの実装。

いくつかの苦渋の後、少なくとも私は、安全でないクラスがおおよそ知るnative方法及びキーチェーンC ++ソースコードを呼び出します。

compareAndSwapInt例えば、用ターンの呼び出しで、この地域のアプローチのOpenJDKのためのC ++コード:

(1)unsafe.cppopenjdk/hotspot/src/share/vm/prims/unsafe.cpp

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  UnsafeWrapper("Unsafe_CompareAndSwapInt");
  oop p = JNIHandles::resolve(obj);
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
复制代码

ソースコードを見るためにクリック

(2)atomic_****_****.inline.hpp

  • あなたはwindows_x86の下で実行されている場合(Windowsシステム、x86プロセッサ)

atomic_windows_x86.inline.hppopenjdk/hotspot/src/os_cpu/windows_x86/vm/atomic_windows_x86.inline.hpp

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}
复制代码

ソースコードを見るためにクリック

  • あなたはlinux_x86の下で実行されている場合(Linuxシステム、x86プロセッサ)
inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  int mp = os::is_MP();
  __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                    : "cc", "memory");
  return exchange_value;
}
复制代码

ソースコードを見るためにクリック

__asm__ スタートのコンパイルを表し

volatile コンパイラの最適化が無効になっています。

LOCK_IF_MP これは、インラインであります

#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "
复制代码

リファレンスは、さらにいくつかのコヨーテ以上からアカウント素人CAS

os::is_MP() この関数は、現在のシステムは、マルチコアプロセッサであるかどうかを決定することです。

これは、アセンブリコード生成されるべき場所であるので、私はちょうどこのラインに焦点を当てますLOCK_IF_MP(%4) "cmpxchgl %1,(%3)"すべての後に、背中を読み取ることができません。

ここでは、現在のシステムが追加されます、マルチコアプロセッサと同等であればlock、命令プリフィックスを、または増加しません。

lock命令プリフィックスの説明:

  • Javaのメモリモデルとスレッドが言及している「Java仮想マシンの深い理解」(371):「キーがあるlock手動プレフィックスクエリIA32、その役割は、このCPUキャッシュメモリが書き込まれ、書き込み操作にすることですまた、他のCPUコアまたはその他の無効化(無効化)そのキャッシュの原因は、この操作は、「ストアと書き込み」操作に言及したJavaメモリモデルの前でプレゼンテーションを行うために、キャッシュ変数と同等であることができます。」
  • インテルIA32マニュアル8.1 LOCKED ATOMIC OPERATIONSロック接頭辞の意味について:
    • それを確保するためのアトミック操作
    • バスロック、LOCK#信号とLOCK命令プリフィックスを使用して、
    • データキャッシュ(キャッシュロック)の原子構造ことを保証するためにキャッシュ・コヒーレンシ・プロトコル、
  • lock命令プリフィックスは、命令の並べ替えを禁止することができる:あなたが読むことができるのIntel IA32マニュアル8.2.2 Memory Ordering in P6 and More Recent Processor Families8.2.3 Examples Illustrating the Memory-Ordering Principlesドローの2の内容を。

CASの実装の基礎となる原理は明らかに、実際にはこれであるだろう、ここを参照してください:lock cmpxchg

cmpxchgハードウェアレベルのアトミック命令、であるlock。このメモリの可視性とdisableコマンドを並べ替えていることを確認するために、プレフィックス命令実行。

lock cmpxchg以来:いくつかの個人的な理解lock命令プリフィックスバス(またはキャッシュのロックを)ロックされますので、CPUはバスが排他状態にある実行し、バスの放送を経由してCPU read invalidateキャッシュ・コヒーレンシ・プロトコル(MESI)を通じて情報、 CPUキャッシュデータの残りの部分を設定するinvalid(キャッシュデータがあれば)、それによってデータに独占権を得、その後行った後の状態cmpxchgデータを変更するアトミック命令を、データの修正が完了しました。

第三に、いくつかの考えと質問

1.今、CASは、揮発性の読み取りと書き込みのメモリセマンティクスを持っていることを、なぜまた変数揮発性を宣言する必要がありますか?

以前の現在のシステムである場合、マルチコアプロセッサを追加することを前記lock命令プレフィックスを、または追加しません。一度に揮発性は、変数代入に追加されますlock指示。図:

揮発性putfield

だから、次のような理由があるかどうか:

  • 単にセット方法を取る、CASのために行ってはいけない、揮発性メモリは割り当ての可視性を保証することができます。
  • シングルコアプロセッサの場合は、CASが追加されていないlock命令、これだけではcmpxchg、各スレッドローカルキャッシュの失敗、それを確保することができますか?volatile変数のCASは、この問題を回避することができるかどうか?

これらは、私の個人的な理解の一部、必ずしも正しい、議論することを歓迎しています。

2.ロックバス上で、マルチスレッドがロックを取得が理由です、それが小さくなるようにサイズです; CPUキャッシュすることができますまた、ローカルの作業用メモリスレッドと同様。

IV結論

最終的に学んだ研究CASは、以下のとおりです。

  • いくつかの表面的なOpenJDKののC ++ソースコードを読むことができます

  • 揮発性の理解を深め

  • ロック命令の役割

  • メモリバリア

  • Javaバイトコードを逆アセンブルする方法

  • およびツールの使用

やりがい!自分で賞賛を与えます!ハッハッハ〜

参考文献:

(1)CASを理解しやすいです

(2)CASの原理のJAVA深さ分析

(3)なぜ、メモリバリア?中国語の翻訳(A)

(4)セクション8.1、8.2マニュアルインテルIA32

おすすめ

転載: juejin.im/post/5e0e21e0f265da5d4170ea4e
おすすめ