This is the 6th day of my participation in the August More Text Challenge

JUC – AtomicInteger analysis

Atomic types are a lock-free, thread-safe solution to multithreaded concurrent data safety that includes both primitive and reference types.

We know that an expression like x=x+1 looks like a single line of code that increments x by one, but the underlying implementation is actually split into three operations: a. Read the value of x. B. Add one to x. C. Write the new value to x. These three operations are atomic by themselves, but not necessarily in series.

An atomic operation is one or more operations that are either all performed without interruption or none performed at all.

So to ensure thread-safe operations, you might add a volatile declaration to x, but the volatile modifier ensures that changes to X are immediately visible across multiple threads and prohibits the Java compiler from reordering the relevant bytecode instructions. There is no guarantee of atomicity or correctness for multiple threads reading and writing x at the same time.

One possible approach is to wrap a non-atomic operation such as x=x+1 with synchronize block. This ensures that when one thread obtains a lock, the other threads must wait for the current thread to complete the lock before attempting to acquire the lock. X =x+1 only one thread is operating at a time.

If synchronize is too heavy for you, you can also use Lock and Lock to achieve better performance than synchronize.

However, it is not cost-effective to modify a set of basic types, either synchronize or Lock. It is more efficient to use atomic types.

AtomicInteger is introduced:

AtomicInteger is in the JUC package. It inherits from the Number class like normal Integer, but is not exactly equivalent to Integer. This class is an extension of the atomic operations on Integer.

Here are some basic apis:

int getAndIncrement() Atomic i++, returns the value before it increments
int incrementAndGet() Atomic plus plus I, returns the increment
GetAndDecrement (), decrementAndGet () Atomic I — and atomic I
boolean compareAndSet(int expect, int update) Atomic x is equal to y. Return Boolean to indicate whether the update succeeded. The update will only happen if the current value matches expect, otherwise it will fail.
Int getAndAdd(int delta), int addAndGet(int delta) AtomicInteger’s value is updated atomically, and the updated value is the sum of value and delta. The method returns a similar value.
Void set(int newValue), void set(int newValue) AtomicInteger has a volatile value field inside it. Set changes to the volatile value are forced to flush to main memory and are immediately visible to other threads. However, the memory barrier of volatile itself is costly to keep threads visible. For example, there is no need to preserve a memory barrier when an AtomicInteger value is modified in a single thread, the value is volatile, and the lazySet method is used to update the value without taking into account situations that are immediately visible to the thread.

Core API analysis:

If we look at AtomicInteger’s source code, we will come across two concepts, CAS algorithm and spin.

CAS:

CAS is full name compara and set. A CAS operation contains three values, the memory value V, the expected value A at the time of modification, and the new value B to be modified. When performing the modification, B will be updated to the memory value V only if V and A are the same, otherwise nothing will be done.

For AtomicInteger, compareAndSet calls the compareAndSetInt method of the Unsafe class.

U.compareAndSetInt(this, VALUE, expectedValue, newValue);
Copy the code

The method has four input parameters. This stands for the object to be operated, that is, the current AtomicInteger object, and VALUE stands for the memory address offset of the AI object with respect to the object. ExpectedV and newV are the A and B mentioned in CAS above.

Unsafe’s compareAndSetInt method needs to be checked in the JDK source code:

Hg.openjdk.java.net/jdk10/jdk10…

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, JNIHandles::resolve(obj); JNIHandles::resolve(obj); Jint * addr = (jint *)index_oop_from_field_offset_long(p, offset); CAS return (jint)(Atomic:: CMPXCHG (x, addr, e)) == e; } UNSAFE_ENDCopy the code

Different versions of the JDK may differ slightly, but they all do the same thing.

Atomic:: CMPXCHG is the assembly code that calls the corresponding CPU architecture.

inline T Atomic::PlatformCmpxchg<4> : :operator()(T exchange_value,
                                                T volatile* dest,
                                                T compare_value,
                                                cmpxchg_memory_order /* order */) const {
  STATIC_ASSERT(4= =sizeof(T));
  __asm__ volatile (  "lock cmpxchgl %1,(%3)"
                    : "=a" (exchange_value)
                    : "r" (exchange_value), "a" (compare_value), "r" (dest)
                    : "cc"."memory");
  return exchange_value;
}
Copy the code

With __ASm__ inline assembly, volatile disables compiler optimization. The LOCK CMPXCHGL instruction was executed. CMPXCHGL is an instruction used to compare and swap operands, but this instruction is not atomic, so it is prefixed with lock (the CPU guarantees the atomicity of the instructions it modifies), which guarantees the consistency of the data being manipulated by the modified instruction through the bus lock or cache consistency.

  1. Bus lock: Blocking requests from other processors by locking the system bus.

  2. Cache consistency: When a processor accesses data cached in another processor, it cannot get the wrong data. If the data is modified, other processors must also get the modified data.

Spin:

Apis such as getAndAddInt must be successfully modified, and it can be tempting to think that we can simply write code like the following:

ai.comparaAndSet(ai.get(), 10)
Copy the code

AtomicInteger, however, is a different mechanism from Synchronize or Lock, which operates at the CPU level to ensure thread-safety when a value is changed, while the latter uses a Lock mechanism to exclude other threads from changing the value. If ai.get() is called by thread A and gets the value, thread B has already updated the new value of ai. In this case, it will cause ai.

But with the incrementAndGet API, the operation must be successful. IncrementAndGet executes the Unsafe getAndAddInt method:

public final int incrementAndGet(a) {
    return U.getAndAddInt(this, VALUE, 1) + 1;
}

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while(! weakCompareAndSetInt(o, offset, v, v + delta));return v;
}
Copy the code

Internally, getIntVolatile is used to obtain the value before the modification, and then deathly loop weakCompareAndSetInt until the modification is successful. WeakCompareAndSetInt actually calls the compareAndSetInt analyzed above.

So in concurrent scenarios, incrementAndGet, the API, doesn’t necessarily get the V+1 at the call point, it might get a larger value.

Here’s a diagram to illustrate the process: