`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 { // Calculates the offset of the variable value in the class object 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 In fact, it is just a shell, and the main logic is encapsulated in Unsafe's * compareAndSwapInt method */ return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } // ...... } public final class Unsafe { // compareAndSwapInt is a method of type native. Let's continue public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); // ...... }`
`// unsafe.cpp /* * This doesn't look like a function, but don't worry, it's not the point. UNSAFE_ENTRY and UNSAFE_END is a macro, * which will be replaced with real code during precompiling. The following jboolean, jlong and jint types are also macros: * * 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); // Calculate the address of value according to the offset. The offset here is the valueOffset in AtomaicInteger jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); // Call the function cmpxchg in Atomic, which is declared in Atomic In 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"); /* * Call overloaded functions on different platforms according to the operating system type. During precompiling, the compiler will decide which platform overloaded * function to call. The related precompiling logic is as follows: * * atomic inline. hpp: * #include "runtime/atomic.hpp" * * // Linux * #ifdef TARGET_ OS_ ARCH_ linux_ X86 * # include "atomic_linux_x86. Inline. HPP" * #endif * * / / omit some code * * / / 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 * * next analyze atomic_ windows_ x86. inline. Implementation of cmpxchg function in HPP */ return (unsigned int)Atomic::cmpxchg((jint)exchange_value, (volatile jint*)dest, (jint)compare_value); }`
The above analysis seems more, but the main process is not complicated. It's easy to understand if you don't worry about the details of the code. Next, I will analyze the Atomic::cmpxchg function on the Windows platform. Keep looking down.
`// 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 } }`
The above code is controlled by LOCK_IF_MP consists of precompiled identifiers and cmpxchg functions. To see more clearly, we'll use lock in the cmpxchg function_ IF_ Replace MP with actual content. As follows:
`inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { // Determine whether it is a multi-core CPU int mp = os::is_MP(); __asm { // Put the parameter value into the register mov edx, dest // Note: dest is the pointer type. Here, the memory address is stored in the edx register mov ecx, exchange_value mov eax, compare_value // LOCK_IF_MP cmp mp, 0 /* * If mp = 0, it indicates that the thread is running in a single core CPU environment. At this time, je will jump to the L0 mark, * that is, cross_ The emit 0xF0 instruction directly executes the cmpxchg instruction. That is, the following cmpxchg instruction * is not prefixed with lock. */ je L0 /* * 0xF0 It is a machine code prefixed with lock. Instead of lock, the form of machine code is directly used. As for the * reasons for doing so, please refer to an answer from Zhihu:* https://www.zhihu.com/question/50878124/answer/123099923 */ _emit 0xF0 L0: /* * Compare and exchange. Briefly explain the following instruction. Friends familiar with assembly can skip the following explanation: * cmpxchg: that is, the "compare and exchange" instruction * dword: the full name is double word. In x86/x64 system, a * word = 2 byte, dword = 4 byte = 32 bit * PTR: the full name is pointer, which is used together with the previous dword, Indicates that the memory unit accessed is a doubleword unit * [edx]: [...] Represents a memory unit, edx is a register, and the dest pointer value is stored in edx* Then [edx] indicates the memory unit with the memory address dest. * this instruction means to compare the value in eax register with the value * in [edx] double word memory unit. If it is the same, store the value in ecx register (exchange_value) in [edx] memory unit. */ cmpxchg dword ptr [edx], ecx } }`
Here, the implementation process of CAS is finished. The implementation of CAS is inseparable from the support of processor. In fact, the core code of the above codes is a cmpxchg instruction with lock prefix, that is, lock cmpxchg dword ptr [edx], ecx.
4.ABA issues
When it comes to CAS, we should basically talk about the ABA of CAS. CAS consists of three steps, namely "read - > compare - > write back". Considering such a situation, thread 1 and thread 2 execute CAS logic at the same time. The execution order of the two threads is as follows:
-
Time 1: thread 1 performs A read operation to obtain the original value A, and then the thread is switched away
-
Time 2: thread 2 completes the CAS operation and changes the original value from A to B
-
Time 3: thread 2 executes CAS operation again and changes the original value from B to A
-
Time 4: thread 1 resumes running, compares the compareValue with the old value, and finds that the two values are equal.
last
For many Java engineers, if they want to improve their skills, they often grope and grow by themselves. The learning effect of fragmentation is inefficient, long and helpless.
I hope these materials can be used as a reference for Java development friends and avoid detours. The focus of this paper is whether you have gained and grown, and the rest is not important. I hope readers can keep this in mind.
Share another wave of my Java interview questions + video learning explanations + advanced skills books
Upgrading skills is often their own exploration and growth, and the learning effect of fragmentation is inefficient, long and helpless**
I hope these materials can be used as a reference for Java development friends and avoid detours. The focus of this paper is whether you have gained and grown, and the rest is not important. I hope readers can keep this in mind.
Share another wave of my Java interview questions + video learning explanations + advanced skills books