The interviewer asked me Java concurrency/multithreading-CAS principle analysis, I answered this and gave me a 30k offer

table of Contents

 

What is CAS

CAS means compare and swap, compare and exchange.

CAS is an atomic operation, and CAS uses an optimistic locking mechanism.

Many functions in JUC are built on CAS, and the bottom layer of various atomic classes uses CAS to achieve atomic operations. Used to solve the security problem of concurrency.

In addition, I have collected more than 20 years of company interview knowledge points, as well as various Java core knowledge points to share with you for free. If you want information, please click (click here) to get it for free !

Concurrency security issues

Give a typical examplei++

public class AddTest {
  public volatile int i;
  public void add() {
    i++;
  }
}

By javap -c AddTestsee bytecode instructions add method:

public void add();
    Code:
       0: aload_0
       1: dup
       2: getfield      #2                  // Field i:I
       5: iconst_1
       6: iadd
       7: putfield      #2                  // Field i:I
      10: return

i++Is split into multiple instructions:

  1. Execute getfieldto get the original memory value;
  2. Execute iaddto add 1 operation;
  3. Execute putfieldwrite to write the accumulated value back to memory.

Assume a situation:

  • When the 线程 1 execution arrives iadd, since it has not been executed yet putfield, the value in the main memory area will not be refreshed at this time.
  • At this time, it 线程 2 enters and starts to run, just copy the value of the main memory area to the private memory area.
  • 线程 1Just execute putfieldand update the value of the main memory area, then 线程 2 the copy at this time is the old one. The error appeared.

How to solve?

The simplest is to add synchronized to the add method.

public class AddTest {
  public volatile int i;
  public synchronized void add() {
    i++;
  }
}

Although simple and solved the problem, the performance is not good.

The best solution should be to use the CAS program that comes with JDK , as in the above example, use the AtomicIntegerclass

public class AddIntTest {
  public AtomicInteger i;
  public void add() {
    i.getAndIncrement();
  }
}

Underlying principle

The principle of CAS is not complicated:

  • Three parameters, a current memory value V, expected value A, updated value B
  • If and only if the expected value A and the memory value V are the same, modify the memory value to B and return true
  • Otherwise do nothing and return false

Take a  AtomicInteger class analysis, first look at the source code:

My environment here is Java11, if it is Java8, some internal names are slightly different.

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    /*
     * This class intended to be implemented using VarHandles, but there
     * are unresolved cyclic startup dependencies.
     */
    private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
    private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");

    private volatile int value;
  
		//...
}

Unsafe Class, this class is rarely useful for general development.

Unsafe The bottom layer of the class is implemented in C/C++, so its methods are all modified by the native keyword.

It can provide hardware-level atomic operations, such as obtaining the location of an attribute in memory and modifying the field value of an object.

key point:

  • AtomicInteger The value stored by the class is in the  value field, and the valuefield isvolatile

  • In the static code block, and get the  Unsafe instance, get the value offset of the  field in memory VALUE

Next back to the previous example:

As above, the getAndIncrement() bottom layer of the method uses CAS technology to ensure concurrency safety.

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

getAndAddInt() method:

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;
}

vgetIntVolatile(o, offset)Get  by  method, its purpose is to get   the value o at the  offsetoffset, which  o is  AtomicInteger the value stored in the class, that is valuethe value of the offset memory offset, ie  VALUE.

Focus , weakCompareAndSetInt it is to realize the  CAS core method

  • If the  o sum is  vequal, it proves that no other thread has changed this variable, and then the  v value is updated to  v + delta, where  delta is the updated incremental value.
  • On the contrary, CAS continues to operate in a spin mode, and this step is also an atomic operation.

analysis:

  • AtomicInteger The original value is set  to A, 线程 1 and 线程 2 each has a copy, the value is A.
  1. 线程 1 By getIntVolatile(o, offset)getting the value A, it 线程 1 is suspended at this time .
  2. 线程 2 The getIntVolatile(o, offset)value A is also obtained through the method, and the weakCompareAndSetIntmethod is executed to compare the memory value as A, and the memory value is successfully modified to B.
  3. At this time , I compare the 线程 1 execution weakCompareAndSetIntmethod and find that the value A in my hand is inconsistent with the value B of the memory, indicating that the value has been modified in advance by other threads.
  4. 线程 1 Re-execute getIntVolatile(o, offset)to get the value value again, because the variable value is modified by volatile and has visibility, thread A continues to execute weakCompareAndSetIntcomparison and replacement until it succeeds

CAS needs attention

Use restrictions

CAS is an atomic operation supported by the CPU. Its atomicity is guaranteed at the hardware level. It cannot be used directly by ordinary users in Java, and can only be used with the aid atomicof atomic classes under the package, with limited flexibility.

However, CAS can only guarantee the atomicity of a single variable operation. When multiple variables are involved, CAS can do nothing.

Atomicity does not necessarily guarantee thread safety. For example, in Java, volatilecooperation is needed to ensure thread safety.

ABA problem

concept

CAS has a problem, an example is as follows:

  • 线程 1 Remove A from memory location V
  • At this time, A is 线程 2 also taken from memory location V
  • At this time 线程 1 in the suspended state, 线程 2 change the value of position V to B, and finally to A
  • 线程 1 Re-execute at this time , and find that the value of position V has not changed, and continue to execute as expected.

Although at this time 线程 1was successful, but it does not meet our real expectations, equal线程 2狸猫换太子to 线程 1playing.

This is the so-called ABA problem

solution

Introduce atomic references, atomic operations with version numbers.

Bring a version number to each of our operations, so that we can avoid ABA problems. Both the idea of ​​optimistic lock.

  • Each time the value in the memory changes, the version number is updated.

  • When performing CAS operations, when comparing the values ​​in the memory, the version numbers are also compared. Only when the two have not changed can the execution succeed.

  • AtomicStampedReferenceClasses in Java use version numbers to solve ABA problems.

Cost issues under high competition

  • In a highly competitive environment with a high probability of concurrent conflicts, if the CAS fails all the time, it will always try again, and the CPU overhead is relatively high.

  • One way of thinking about this problem is to introduce an exit mechanism, such as failing to exit after the number of retries exceeds a certain threshold.

  • More importantly, avoid using optimistic locking in a highly competitive environment.

In addition, I have collected more than 20 years of company interview knowledge points, as well as various Java core knowledge points to share with you for free. If you want information, please click (click here) to get it for free !

Guess you like

Origin blog.csdn.net/m0_46757769/article/details/112834716