65- The relationship between CAS and optimistic locking, when will CAS be used?

Concurrent container

Doug Lea uses CAS technology extensively in the JUC package. This technology can not only ensure security, but also does not require the use of mutual exclusion locks, which can greatly improve the performance of tools. Below I will use two examples to show the use of CAS in concurrent containers.

Case 1: ConcurrentHashMap

Let's take a look at the example of the concurrent container ConcurrentHashMap. We intercept part of the code of the putVal method, as shown below:

final V putVal(K key, V value, boolean onlyIfAbsent) {
    
    
    if (key == null || value == null) throw new NullPointerException();
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
    
    
        Node<K,V> f; int n, i, fh;
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    
    
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
    //以下部分省略
    ...
}

In line 10, there is an eye-catching method, which is "casTabAt". The method name has "CAS". It can be guessed that it must be inseparable from CAS. The code implementation of the casTabAt method is given below:

static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
                                    Node<K,V> c, Node<K,V> v) {
    
    
    return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

There is only one line of code in this method, which is to call the compareAndSwapObject method of variable U. Then, what type of variable U is? The definition of U is:

private static final sun.misc.Unsafe U 

It can be seen that U is of the Unsafe type. The Unsafe class includes compareAndSwapInt, compareAndSwapLong, compareAndSwapObject and other native layer methods closely related to CAS. The bottom layer is implemented by the support of the CPU for CAS instructions.

The casTabAt method introduced above is not only used in the putVal method of ConcurrentHashMap, but also used in important methods such as merge, compute, computeIfAbsent, transfer, etc. Therefore, ConcurrentHashMap is widely used for CAS.

Case 2: ConcurrentLinkedQueue

Next, let's look at the second case of concurrent containers. There is also CAS in the offer method of the non-blocking concurrent queue ConcurrentLinkedQueue. The code of the offer method is as follows:

public boolean offer(E e) {
    
    
    checkNotNull(e);
    final Node<E> newNode = new Node<E>(e);
    for (Node<E> t = tail, p = t;;) {
    
    
        Node<E> q = p.next;
        if (q == null) {
    
    
            if (p.casNext(null, newNode)) {
    
    
                if (p != t) 
                    casTail(t, newNode); 
                return true;
            }
        }
        else if (p == q)
            p = (t != (t = tail)) ? t : head;
        else
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

It can be seen that in the offer method, there is a for loop, which is an infinite loop. In line 8, there is a CAS-related method, which is the casNext method, which is used to update nodes. Then if the casNext method of p fails, casNext will return false, so obviously the code will continue to try the next time in the for loop. So here it is also obvious that the offer method of ConcurrentLinkedQueue uses CAS.

The above are two examples of CAS applications in concurrent containers. Let's take a look at the applications of CAS in databases.

database

In our database, there are also applications of optimistic locking and CAS ideas. When updating data, we can use the version field to implement optimistic locking and CAS operations in the database, and we do not need to add pessimistic locking when acquiring and modifying data.

The specific idea is as follows: When we have obtained the data and completed the calculation, when we are ready to update the data, we will check whether the current version number is consistent with the version number when the data was previously obtained. Update the data directly; if the version numbers are inconsistent, it means that other threads have modified the data during the calculation period, and you can choose to reacquire the data, recalculate, and then try to update the data again.

Assuming that the version is 1 when the data is retrieved, the corresponding SQL statement example is as follows:

UPDATE student SET   name = ‘小王’,  version = 2   WHERE  id = 10  AND version = 1 

In this way, the idea of ​​CAS can be used to implement this update operation. It will first compare whether the version is the first obtained 1, and only modify the name field if it is the same as the initial value. At the same time, the version must be changed. The value of plus one.

Atomic class

In atomic classes, such as AtomicInteger, CAS is also used. We have analyzed the content of atomic classes in class 39. Now we review the key content related to CAS, that is, the getAndAdd method of AtomicInteger, the method code As follows:

public final int getAndAdd(int delta) {
    
        
    return unsafe.getAndAddInt(this, valueOffset, delta);
}

As you can see from the three lines of code above, the content of return is the execution result of Unsafe's getAndAddInt method. Next, let's take a look at the specific implementation of the getAndAddInt method. The code is as follows:

public final int getAndAddInt(Object var1, long var2, int var4) {
    
    
    int var5;
    do {
    
    
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

Here, we see that there is an assignment to var5 in the above method, and the unsafe getIntVolatile(var1, var2) method is called. This is a native method, which is used to obtain the value of the offset var2 in the variable var1. Here var1 is a reference to the AtomicInteger object, and var2 is the offset valueOffset of the value (that is, value) stored in AtomicInteger, so the var5 obtained at this time actually represents the value stored in the atomic class at the current moment .

The next point is here, we see that there is a compareAndSwapInt method, where multiple parameters will be passed in, namely var1, var2, var5, var5 + var4, in fact, they represent object, offset, expectedValue and newValue.

  • The first parameter object is the object to be modified. The passed in is this, which is the atomicInteger object itself;
  • The second parameter is offset, which is the offset, with which you can get the value of value;
  • The third parameter, expectedValue, represents the "expected value", and the var5 obtained just now is passed in;
  • The last parameter, newValue, is the new value that we want to modify, which is equal to the previously obtained value var5 plus var4, and var4 is the delta we passed in before, delta is the value we want the atom class to change, for example, it can be passed Enter +1, or -1.

So the function of the compareAndSwapInt method is to determine if the value of the value in the atomic class is equal to the previously obtained var5, then update the calculated var5 + var4, so this line of code implements the CAS process.

Once the CAS operation is successful, the while loop will exit, but the operation may also fail. If the operation fails, it means that after obtaining var5 and before the CAS operation, the value of value has changed, which proves that other threads have modified this variable.

In this way, the code in the loop body will be executed again to get the value of var5 again, that is, get the value of the latest atomic variable, and use CAS again to try to update until the update is successful, so this is an infinite loop.

Let's summarize, Unsafe's getAndAddInt method is implemented by loop + CAS . During this process, it will try to update the value of value through the compareAndSwapInt method. If the update fails, it will get it again, and then try to update again until it is updated. success.

to sum up

Shared the application scenarios of CAS. There are many codes related to CAS in concurrent containers, databases, and atomic classes. Therefore, CAS has a wide range of application scenarios. You need to clearly understand under what circumstances CAS is used.

Guess you like

Origin blog.csdn.net/Rinvay_Cui/article/details/111059146