Java の基本チュートリアルカラムの紹介と分析 Java CAS
1. はじめに
CAS は、compare and swap の略です。マルチスレッド環境で同期機能を実装するためのものです。 CAS 操作には、メモリ位置、期待値、新しい値という 3 つのオペランドが含まれます。 CAS の実装ロジックでは、メモリ位置の値と期待値を比較し、それらが等しい場合、メモリ位置の値を新しい値に置き換えます。等しくない場合、操作は実行されません。 Java では、Java は CAS を直接実装せず、CAS 関連の実装は C インライン アセンブリの形式で実装されます。 Java コードは JNI 経由で呼び出す必要があります。第 3 章で実装の詳細を分析します。 前述したように、CAS の操作プロセスは難しくありません。しかし、上記の説明だけでは十分ではないので、次にその他の予備知識をいくつか紹介します。この背景知識があるだけで、その後の内容をよりよく理解できます。2. 背景の紹介
CPU がバスとメモリを介してデータを送信することは誰もが知っています。マルチコア時代では、複数のコアが同じバスを介してメモリや他のハードウェアと通信します。以下に示すように:inc dword ptr [...] は
DEST = DEST 1 と同等です。この命令には、2 つのメモリ アクセスを伴う 3 つの操作
Read->Modify->Writeが含まれています。値 1 がメモリ内の指定された場所に格納されている状況を考えてみましょう。これで、両方の CPU コアが同時に命令を実行します。 2 つのコアの交互実行のプロセスは次のとおりです。
- コア 1 はメモリ内の指定された場所から値 1 を読み取り、レジスタにロードします。
- コア 2 は読み取りますメモリ内の指定された場所から値 1 を取得し、レジスタにロードします。
- コア 1 レジスタの値を 1
- だけデクリメントします。コア 2 レジスタの値を 1
- だけデクリメントします。 コア 1 変更した値をメモリに書き戻す
- コア 2 変更した値をメモリに書き戻す
付随する命令の実行中にプロセッサの LOCK# 信号をアサートします (説明されている重要なポイントマルチプロセッサ環境では、LOCK# 信号によりプロセッサが一部の共有メモリを排他的に使用できることが太字で強調表示されています。ロックは、次の命令の前に追加できます: ADD、ADC、AND、BTC、BTR、BTS、CMPXCHG、CMPXCH8B、CMPXCHG16B、DEC、INC、NEG、NOT、OR、SBB、SUB、XOR、XADD 、XCHG.
命令を次のように変換します)。アトミック命令 ) マルチプロセッサ環境では、LOCK# 信号 は、信号がアサートされている間、プロセッサが共有メモリを排他的に使用できるようにします 。
inc 命令の前にロック プレフィックスを追加すると、命令をアトミックにすることができます。複数のコアが同じ inc 命令を同時に実行する場合、それらはシリアル方式で実行されるため、上記の状況は回避されます。ここで別の質問がありますが、ロック プレフィックスはどのようにしてコアが特定のメモリ領域を排他的に占有することを保証するのでしょうか?答えは次のとおりです。
Intel プロセッサでは、プロセッサの特定のコアが特定のメモリ領域を排他的に占有するようにする方法が 2 つあります。 1 つ目の方法は、バスをロックして特定のコアにバスを独占的に使用させることですが、これはコストがかかりすぎます。バスがロックされると、他のコアはメモリにアクセスできなくなり、他のコアが短時間動作を停止する可能性があります。 2 番目の方法は、一部のメモリ データがプロセッサ キャッシュにキャッシュされている場合に、キャッシュをロックすることです。プロセッサが発行する LOCK# 信号はバスをロックするのではなく、キャッシュ ラインに対応するメモリ領域をロックします。このメモリ領域がロックされている間、他のプロセッサはこのメモリ領域に対して関連する操作を実行できません。バスをロックする場合と比較して、キャッシュをロックするコストは明らかに小さくなります。バス ロックとキャッシュ ロックの詳細については、Intel Developer's Manual Volume 3 Software Developer's Manual、第 8 章マルチプロセッサ管理を参照してください。
3. ソース コードの分析
上記の背景知識を踏まえて、CAS のソース コードをゆっくり読むことができます。この章の内容では、java.util.concurrent.atomic パッケージにあるアトミック クラス AtomicInteger の CompareAndSet メソッドを分析します。関連する分析は次のとおりです:
public class AtomicInteger extends Number implements java.io.Serializable { // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { // 计算变量 value 在类对象中的偏移 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; public final boolean compareAndSet(int expect, int update) { /* * compareAndSet 实际上只是一个壳子,主要的逻辑封装在 Unsafe 的 * compareAndSwapInt 方法中 */ return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } // ...... } public final class Unsafe { // compareAndSwapInt 是 native 类型的方法,继续往下看 public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); // ...... }
// unsafe.cpp /* * 这个看起来好像不像一个函数,不过不用担心,不是重点。UNSAFE_ENTRY 和 UNSAFE_END 都是宏, * 在预编译期间会被替换成真正的代码。下面的 jboolean、jlong 和 jint 等是一些类型定义(typedef): * * jni.h * typedef unsigned char jboolean; * typedef unsigned short jchar; * typedef short jshort; * typedef float jfloat; * typedef double jdouble; * * jni_md.h * typedef int jint; * #ifdef _LP64 // 64-bit * typedef long jlong; * #else * typedef long long jlong; * #endif * typedef signed char jbyte; */ 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); // 根据偏移量,计算 value 的地址。这里的 offset 就是 AtomaicInteger 中的 valueOffset jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); // 调用 Atomic 中的函数 cmpxchg,该函数声明于 Atomic.hpp 中 return (jint)(Atomic::cmpxchg(x, addr, e)) == e; UNSAFE_END // atomic.cpp unsigned Atomic::cmpxchg(unsigned int exchange_value, volatile unsigned int* dest, unsigned int compare_value) { assert(sizeof(unsigned int) == sizeof(jint), "more work to do"); /* * 根据操作系统类型调用不同平台下的重载函数,这个在预编译期间编译器会决定调用哪个平台下的重载 * 函数。相关的预编译逻辑如下: * * atomic.inline.hpp: * #include "runtime/atomic.hpp" * * // Linux * #ifdef TARGET_OS_ARCH_linux_x86 * # include "atomic_linux_x86.inline.hpp" * #endif * * // 省略部分代码 * * // Windows * #ifdef TARGET_OS_ARCH_windows_x86 * # include "atomic_windows_x86.inline.hpp" * #endif * * // BSD * #ifdef TARGET_OS_ARCH_bsd_x86 * # include "atomic_bsd_x86.inline.hpp" * #endif * * 接下来分析 atomic_windows_x86.inline.hpp 中的 cmpxchg 函数实现 */ return (unsigned int)Atomic::cmpxchg((jint)exchange_value, (volatile jint*)dest, (jint)compare_value); }
上記の分析はさらに多いようですが、主な処理は複雑ではありません。コードの詳細にこだわらなければ、比較的簡単に理解できます。次に、Windows プラットフォームでの Atomic::cmpxchg 関数を分析します。読む。
// atomic_windows_x86.inline.hpp #define LOCK_IF_MP(mp) __asm cmp mp, 0 \ __asm je L0 \ __asm _emit 0xF0 \ __asm L0: 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 } }
上記のコードは、LOCK_IF_MP プリコンパイル済み識別子と cmpxchg 関数で構成されています。もう少しわかりやすくするために、cmpxchg 関数の LOCK_IF_MP を実際の内容に置き換えてみましょう。
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { // 判断是否是多核 CPU int mp = os::is_MP(); __asm { // 将参数值放入寄存器中 mov edx, dest // 注意: dest 是指针类型,这里是把内存地址存入 edx 寄存器中 mov ecx, exchange_value mov eax, compare_value // LOCK_IF_MP cmp mp, 0 /* * 如果 mp = 0,表明是线程运行在单核 CPU 环境下。此时 je 会跳转到 L0 标记处, * 也就是越过 _emit 0xF0 指令,直接执行 cmpxchg 指令。也就是不在下面的 cmpxchg 指令 * 前加 lock 前缀。 */ je L0 /* * 0xF0 是 lock 前缀的机器码,这里没有使用 lock,而是直接使用了机器码的形式。至于这样做的 * 原因可以参考知乎的一个回答: * https://www.zhihu.com/question/50878124/answer/123099923 */ _emit 0xF0 L0: /* * 比较并交换。简单解释一下下面这条指令,熟悉汇编的朋友可以略过下面的解释: * cmpxchg: 即“比较并交换”指令 * dword: 全称是 double word,在 x86/x64 体系中,一个 * word = 2 byte,dword = 4 byte = 32 bit * ptr: 全称是 pointer,与前面的 dword 连起来使用,表明访问的内存单元是一个双字单元 * [edx]: [...] 表示一个内存单元,edx 是寄存器,dest 指针值存放在 edx 中。 * 那么 [edx] 表示内存地址为 dest 的内存单元 * * 这一条指令的意思就是,将 eax 寄存器中的值(compare_value)与 [edx] 双字内存单元中的值 * 进行对比,如果相同,则将 ecx 寄存器中的值(exchange_value)存入 [edx] 内存单元中。 */ cmpxchg dword ptr [edx], ecx } }
CAS の実装プロセスはここで終了です CAS の実装はプロセッサのサポートと切り離せません。上記には非常に多くのコードがありますが、コア コードは実際にはロック プレフィックスが付いた cmpxchg 命令、つまり lock cmpxchg dword ptr [edx], ecx
です。
4. ABA 問題
CAS について話すときは、基本的に CAS の ABA 問題について話さなければなりません。 CAS は、「読み取り -> 比較 -> ライトバック」という 3 つのステップで構成されます。スレッド 1 とスレッド 2 が CAS ロジックを同時に実行する状況を考えます。2 つのスレッドの実行シーケンスは次のとおりです:
- 時間 1: スレッド 1 が読み取り操作を実行し、オリジナルの値 A に戻り、スレッドがスイッチド アウェイになりました。
- 時間 2: スレッド 2 が CAS 操作を完了し、元の値を A から B に変更します。
- 時間 3: スレッド 2 が再度 CAS 操作を実行し、値が変更されます。元の値を B から A
- 瞬間 4: スレッド 1 が実行を再開し、比較値 (compareValue) と元の値 (oldValue) を比較し、2 つの値が等しいことがわかります。次に、新しい値 (newValue) をメモリに書き込み、CAS 操作を完了します。
上記のプロセスと同様、スレッド 1 は元の値が変更されたことを認識せず、変更されているように見えます。変化がないため、プロセスは引き続き実行されます。 ABA の問題の場合、通常の解決策は、CAS 操作ごとにバージョン番号を設定することです。 java.util.concurrent.atomic パッケージは、ABA の問題を処理できるアトミック クラス AtomicStampedReference を提供します。特定の実装はここでは分析されません。興味のある友人は自分で確認してください。
5. まとめ
これを書きながら、この記事もいよいよ終わりに近づいてきました。 CAS の原理自体は実装も含めて難しくありませんが、実際に書くのは簡単ではありません。これには低レベルの知識が必要で、理解はできますが、理解するのはまだ少し難しいです。私には基礎知識が不足しているため、上記の分析の一部は必然的に間違っている可能性があります。間違いがある場合は、お気軽にコメントしてください。もちろん、なぜ間違っているのかを説明するのが最善です。ありがとうございます。
さて、この記事はここまでです。読んでくれてありがとう、それではさようなら。
付録
前のソース コード分析セクションで使用したいくつかのファイルへのパスがここに掲載されています。次のように、誰もがインデックスを付けるのに役立ちます:
ファイル名 | パス |
---|---|
Unsafe.java | openjdk/jdk/src/share/classes/sun/misc/Unsafe.java |
unsafe.cpp | openjdk/ hotspot/src/share/vm/prims/unsafe.cpp |
atomic.cpp | openjdk/hotspot/src/share/vm/runtime/atomic.cpp |
openjdk/hotspot/src/os_cpu/windows_x86/vm/atomic_windows_x86.inline.hpp |
以上がJava CAS 原則分析の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

Go语言是一种开源编程语言,由Google开发并于2009年面世。这种语言在近年来越发受到关注,并被广泛用于开发网络服务、云计算等领域。Go语言最具特色的特点之一是它内置了goroutine(协程),这是一种轻量级的线程,可以在代码中方便地实现并发和并行计算。那么goroutine到底是什么呢?简单来说,goroutine就是Go语言中的

Java作为一种高级编程语言,在并发编程中有着广泛的应用。在多线程环境下,为了保证数据的正确性和一致性,Java采用了锁机制。本文将从锁的概念、类型、实现方式和使用场景等方面对Java中的锁机制进行探讨。一、锁的概念锁是一种同步机制,用于控制多个线程之间对共享资源的访问。在多线程环境下,线程的执行是并发的,多个线程可能会同时修改同一数据,这就会导致数

Python是一门流行的高级编程语言,它具有简单易懂的语法、丰富的标准库和开源社区的支持,而且还支持多种编程范式,例如面向对象编程、函数式编程等。尤其是Python在数据处理、机器学习、科学计算等领域有着广泛的应用。然而,在多线程或多进程编程中,Python也存在一些问题。其中之一就是并发不安全。本文将从以下几个方面介绍如何解决Python的函数中的并发不安

通过CAS(CentralAuthenticationService)实现PHP安全验证随着互联网的快速发展,用户权限管理和身份验证越来越重要。在开发WEB应用程序时,保护用户数据和防止未经授权访问是至关重要的。为了实现这一目标,我们可以使用CAS(CentralAuthenticationService)来进行PHP的安全验证。CAS

随着现代互联网技术的不断发展,网站访问量越来越大,对于服务器的并发处理能力也提出了更高的要求。如何提高服务器的并发处理能力是每个开发者需要面对的问题。在这个背景下,PHP8.0引入了Fibers这一全新的特性,让PHP开发者掌握一种全新的并发处理方式。Fibers是什么?首先,我们需要了解什么是Fibers。Fibers是一种轻量级的线程,可以高效地支持PH

Java作为一种高级语言,在编程语言中使用广泛。在Java的应用程序和框架的开发中,我们经常会碰到并发的问题。并发问题是指当多个线程同时对同一个对象进行操作时,会产生一些意想不到的结果,这些问题称为并发问题。其中的一个常见的异常就是java.util.ConcurrentModificationException异常,那么我们在开发过程中如何有效地解决这个异

使用Go和Goroutines实现高效的并发图计算引言:随着大数据时代的到来,图计算问题也成为了一个热门的研究领域。在图计算中,图的顶点和边之间的关系非常复杂,因此如果采用传统的串行方法进行计算,往往会遇到性能瓶颈。为了提高计算效率,我们可以利用并发编程的方法使用多个线程同时进行计算。今天我将向大家介绍使用Go和Goroutines实现高效的并发图计算的方法

1、说明当多个线程同时对某个资源进行CAS操作时,只有一个线程成功,但不会堵塞其他线程,其他线程只会收到操作失败的信号。可见CAS其实是一个乐观的锁。2、实例跟随AtomInteger的代码,我们可以发现最终调用的是sum.misc.Unsafe。看看Unsafe这个名字,它是一个不安全的类别,它利用了Java类别和可见性规则中恰到好处的漏洞。为了速度,Unsafe在Java的安全标准上做出了一些妥协。publicfinalnativebooleancompareAndSwapInt(Objec


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

WebStorm Mac版
便利なJavaScript開発ツール

Dreamweaver Mac版
ビジュアル Web 開発ツール

Safe Exam Browser
Safe Exam Browser は、オンライン試験を安全に受験するための安全なブラウザ環境です。このソフトウェアは、あらゆるコンピュータを安全なワークステーションに変えます。あらゆるユーティリティへのアクセスを制御し、学生が無許可のリソースを使用するのを防ぎます。

VSCode Windows 64 ビットのダウンロード
Microsoft によって発売された無料で強力な IDE エディター

メモ帳++7.3.1
使いやすく無料のコードエディター

ホットトピック



