This is the 23rd day of my participation in the August More Text Challenge

Phase to recommend

  • Java Basics
  • Java concurrent programming

A, CAS

1. What is CAS?

CAS is short for Compare And Swap, which enables the CPU to Compare whether a certain value in the memory is the same as the expected value. If the value is the same, the CPU will update the value to the new value. If the value is different, the CPU will not update the value.

CAS operations are atomic (both read and write are atomic), so when multiple threads concurrently use CAS to update data, they can do so without locking. The JDK makes extensive use of CAS to update data while preventing synchronized heavyweight locking to keep atoms updated.

Its implementation is accomplished by calling CPU instructions with the help of C/C++, so it is very efficient.

Simply speaking, the CAS operation involves three values: E to be modified, V to be modified, and the value N of the memory location corresponding to the value to be modified.

(1) First read and record the value E (2) to be modified and then calculate the new value V (3) compare the value E and the memory location, if equal -> (4); If not equal -> (5); (4) Update to a new value (5) Update fails

2. CAS usage

If CAS is not used and multiple threads modify the value of a variable at the same time under high concurrency, synchronized locking is required

public class Test {
    private int i=0;
    public synchronized int add(){
        returni++; }}Copy the code

Java provides us with AtomicInteger atomic class (bottom based on CAS to update data), without locking to achieve data consistency in multi-threaded concurrent scenarios.

public class Test {
    private  AtomicInteger i = new AtomicInteger(0);
    public int add(){
        return i.addAndGet(1); }}Copy the code

3. The problem of CAS

Although CAS achieves atomic operation efficiently, it also has some disadvantages, mainly in the following three aspects.

  • Long cycle time and high overhead

If the CAS operation fails, it needs to loop through the CAS operation (loop through and update the expected value to the latest), which can cause a lot of CPU overhead if it fails for a long time.

Solution: Limit the number of spins to prevent an infinite loop.

  • Atomic operations of only one shared variable are guaranteed

When performing operations on a shared variable, we can loop CAS to ensure atomic operations, but when performing operations on multiple shared variables, the loop CAS cannot guarantee atomic operations, so we can use locks.

Solution: If you need to operate on multiple shared variables, you can use pessimistic locking to ensure atomicity, or you can combine multiple shared variables into a single shared variable for CAS operation.

  • ABA problem

ABA problems with CAS(compare and swap) occur in multi-threaded scenarios, such as when two threads change the value of A variable at the same time, and thread 1 and thread 2 both change the variable from A to B. Thread 2 was suspended for some reason. Thread 1 changed the variable value from A to B through CAS(Comparent and Swap). After thread 1 updated the variable value, thread 3 happened to enter. Thread 3 changes the variable value from B to A through comparent And Swap (CAS). After thread 3 completes the update, thread 2 obtains the time slice And continues to execute. Thread 3 changes the variable value from A to B through comparent And Swap (CAS). Thread 2 does not know that the variable has changed A->B->A. This is the ABA problem in CAS.

Workaround: Use version numbers. Append the version number to the variable, increment the version number by 1 each time the variable is updated, and A->B->A becomes 1A->2B->3A.

UnSafe, its food is UnSafe

2.1 Introduction to the UnSafe class

Unsafe, as its name implies, is an Unsafe class under Sun. misc. The Unsafe class does not operate under the Java standard, and most of Java’s memory operations are handed over to the JVM. The Unsafe class has the ability to manipulate memory directly, like Pointers in C. It also brings up the problem of Pointers. Overusing the Unsafe class, which is why it is officially named Unsafe, makes errors even more likely, and is not recommended, even without annotations.

UnSafe class overview:

As shown in the preceding image, the Unsafe Class features memory operations, CAS, class-related, object operations, array related, memory barriers, system related, and thread scheduling.

2.2 the UnSafe and CAS

public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
  {
    int i;
    do
      i = getIntVolatile(paramObject, paramLong);
    while(! compareAndSwapInt(paramObject, paramLong, i, i + paramInt));return i;
  }

  public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)
  {
    long l;
    do
      l = getLongVolatile(paramObject, paramLong1);
    while(! compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));return l;
  }

  public final int getAndSetInt(Object paramObject, long paramLong, int paramInt)
  {
    int i;
    do
      i = getIntVolatile(paramObject, paramLong);
    while(! compareAndSwapInt(paramObject, paramLong, i, paramInt));return i;
  }

  public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2)
  {
    long l;
    do
      l = getLongVolatile(paramObject, paramLong1);
    while(! compareAndSwapLong(paramObject, paramLong1, l, paramLong2));return l;
  }

  public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2)
  {
    Object localObject;
    do
      localObject = getObjectVolatile(paramObject1, paramLong);
    while(! compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));return localObject;
  }
Copy the code

Found in the source code, internal use of spin CAS update (while loop CAS update, if the update fails, the loop again retry).

As you can see from the Unsafe class, atomic operations only support the following three methods.

public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);

public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);

public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
Copy the code

Unsafe only provides three CAS methods:

  1. compareAndSwapObject
  2. compareAndSwapInt
  3. compareAndSwapLong

Typical applications

CAS in Java. Util. Concurrent. Atomic related classes, Java AQS, CurrentHashMap implementation has a very wide application. As shown in the figure below, in the implementation of AtomicInteger, the static field valueOffset is the memory offset address of the field value. The value of valueOffset when AtomicInteger is initialized, Retrieved from the Unsafe objectFieldOffset method in static code blocks. In the thread-safe method provided in AtomicInteger, the value of the field valueOffset can be used to locate the memory address of the value in the AtomicInteger object so that atomic operations on the value field can be implemented according to CAS.

The following figure shows the memory diagram of an AtomicInteger object before and after the increment operation. The baseAddress of the object is “baseAddress= 0x110000”. Run the baseAddress+valueOffset command to obtain the memory address of the value valueAddress= 0x11000C. The atomic update operation is then performed through CAS, and returns if the update is successful, otherwise retry until the update is successful.

2.3 Other features UnSafe

public native long staticFieldOffset(Field paramField);
/* To get the memory address offset of the given paramField */
public native int arrayBaseOffset(Class paramClass);
/* To get the offset address of the first element */
public native int arrayIndexScale(Class paramClass);
/* The conversion factor used to get the incremental address of the element */
public native long allocateMemory(long paramLong);
/* To allocate memory */
public native long reallocateMemory(long paramLong1, long paramLong2);
/* To expand memory */
public native void freeMemory(long paramLong);
/* To free up memory */
Copy the code

CAS atom class AtomicInteger

AtomicInteger is Java. The util. Concurrent. An atomic classes under the atomic package, the package AtomicBoolean, are still under the AtomicLong, AtomicLongArray, AtomicReference atomic classes, It is used to ensure thread safety in a high concurrency environment.

3.1 Common Methods (API)

Public Final int getAndSet(int newValue) public Final int getAndSet(int newValue) Public Final int getAndIncrement() public Final int getAndIncrement() Public Final Int getAndDecrement() : Retrieves the current value and adds the expected valuevoidLazySet (int newValue): This is eventually set to newValue. Using lazySet may cause other threads to read the old value for a short time.Copy the code

3.2 Case Code

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDemo implements Runnable {
    private static final AtomicInteger atomicInteger = new AtomicInteger();

    // Increase the specified number
    public void getAndAdd() {
        atomicInteger.getAndAdd(-90);
    }
    //增加1
    public void getAndIncrement() {
        atomicInteger.getAndIncrement();
    }
    / / reduced by 1
    public void getAndDecrement() {
        atomicInteger.getAndDecrement();
    }
    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerDemo r = new AtomicIntegerDemo();
        Thread t1 = new Thread(r);
        t1.start();
        t1.join();
        System.out.println("AtomicInteger operation result:" + atomicInteger.get());
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) { getAndDecrement(); }}}Copy the code

3.3 Source Code Parsing

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    static {
        try {
            // Get the offset of the value field relative to the start address of the current object
            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    // Return current value
    public final int get() {
        return value;
    }

    // Add detla
    public final int getAndAdd(int delta) {
        // Three parameters: 1, the current instance; 2, the offset of the value instance variable; 3, the number to be added to the current value (value+delta).
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

    // Increments by 1
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }... }Copy the code

We can see that AtomicInteger uses volatile variables and CAS underneath to change the data.

  • Volatile ensures thread visibility. When multiple threads are running concurrently, one thread changes data and the other threads immediately see the changed value
  • CAS guarantees atomicity of data updates.

Four, “said

Due to the limited level of the author, you are welcome to feedback and correct the article is not correct, thanks to 🙏

The likes of my friends are the biggest support for me. If I gain something from reading this article, I hope I can like it, comment on it and pay attention to it.

reference

  • Java magic classes: Unsafe Application parsing
  • In-depth Understanding of the Java Virtual Machine (Version 2)