AtomicInteger source code analysis - CAS-based optimistic locking implementation

1 Pessimistic locking and optimistic locking

We all know that the cpu is time-division multiplexed, that is, the time slice of the cpu is allocated to different threads/processes for execution in turn. Switching involves clearing registers, buffering data. Then reload the data required by the new thread. When a thread is suspended, it is added to the blocking queue, and at a certain time or condition, it wakes up through notify() and notifyAll(). When a resource is unavailable, the CPU is given up and the current waiting thread is switched to the blocking state. Wait until the resource (such as a shared data) is available, then wake up the thread and let it enter the runnable state to wait for cpu scheduling. This is the implementation of a typical pessimistic lock. An exclusive lock is a pessimistic lock, and synchronized is an exclusive lock, which assumes the worst case, and executes only when it is guaranteed that other threads will not interfere, which will cause all other threads that need the lock to hang, waiting for the hold. The thread that has the lock releases the lock.

However, due to the large overhead in the process of suspending and resuming execution. While a thread is waiting for a lock, it can't do anything, so pessimistic locking has a big disadvantage. For example, if a thread needs a resource, but the resource is occupied for a short time, when the thread preempts the resource for the first time, the resource may be occupied. If the thread is suspended at this time, the resource may be found immediately. It is available, and then it takes a long time to re-preempt the lock, and the time cost will be very high.

So there is the concept of optimistic locking. His core idea is to complete an operation every time without locking but assuming there is no conflict. If it fails due to conflict, try again until it succeeds. In the above example, a thread can not give up the cpu, but keep a while loop, and retry if it fails until it succeeds. Therefore, optimistic locking works better when data contention is not severe. For example, CAS is an application of optimistic locking.

2 Implementation of CAS in java

CAS means Compare and Swap, compare and operate. Many CPUs directly support CAS instructions. CAS is an optimistic locking technology. When multiple threads try to use CAS to update the same variable at the same time, only one of the threads can update the value of the variable, while other threads fail. The failed thread will not be suspended, but will be blocked. Informed that you have failed this competition and can try again. CAS has 3 operands, the memory value V, the old expected value A, and the new value B to be modified. Modify memory value V to B if and only if expected value A and memory value V are the same, otherwise do nothing.

JDK1.5 introduced low-level support, exposing CAS operations on types such as int, long, and object references, and the JVM compiles them into the most efficient method provided by the underlying hardware. On the platform running CAS, The runtime compiles them into corresponding machine instructions. All atomic variable types under the java.util.concurrent.atomic package, such as AtomicInteger, use these underlying JVM support to provide an efficient CAS operation for numeric reference types.

In CAS operation, the ABA problem occurs. That is, if the value of V first changes from A to B, and then from B to A, then it is still considered that a change has occurred, and the steps in the algorithm need to be re-executed. There is a simple solution: instead of updating the value of a reference, update two values, including a reference and a version number, even if the value changes from A to B and then to A, the version numbers are different. AtomicStampedReference and AtomicMarkableReference support performing atomic conditional updates on two variables. AtomicStampedReference updates an "object-reference" 2-tuple, avoiding the ABA problem by adding a "version number" to the reference, and AtomicMarkableReference will update an "object-reference-boolean" 2-tuple.

3 AtomicInteger implementation.

AtomicInteger is an Integer class that supports atomic operations, which ensures that the addition and reduction of AtomicInteger type variables are atomic, and there will be no data inconsistency under multiple threads. If AtomicInteger is not used, to achieve an ID that is acquired sequentially, it is necessary to perform a locking operation every time it is acquired to avoid the phenomenon that the same ID is acquired concurrently.

Next, let's look at the source code to see how AtomicInteger implements atomic operations.

First look at the incrementAndGet() method, the following is the specific code.

public final int incrementAndGet() {  
        for (;;) {  
            int current = get();  
            int next = current + 1;  
            if (compareAndSet(current, next))  
                return next;  
        }  
    }

From the source code, you can know that the method of this method is to first obtain the current value attribute value, then add 1 to the value, and assign it to a local next variable. However, these two steps are not thread-safe, but there is a dead internal Loop, keep doing the compareAndSet operation until it succeeds, that is, the fundamental modification is in the compareAndSet method. The code of the compareAndSet() method is as follows:

public final boolean compareAndSet(int expect, int update) {  
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
    }

The declaration of the compareAndSwapInt() method called by the compareAndSet() method is as follows, which is a native method.

publicfinal native boolean compareAndSwapInt(Object var1, long var2, int var4, intvar5);

What compareAndSet passes in is the value attribute value obtained when the method is executed, next is the value after adding 1, what compareAndSet does is to call the compareAndSwapInt method of Sun's UnSafe to complete, this method is a native method, and compareAndSwapInt is based on the CPU CAS instruction to achieve. Therefore, CAS-based operations can be considered non-blocking, and the failure or suspension of one thread will not cause other threads to fail or be suspended. And since CAS operations are CPU primitives, the performance is better.

Similarly, there is the decrementAndGet() method. The difference between it and incrementAndGet() is that the value is decremented by 1 and assigned to the next variable.

There are also getAndIncrement() and getAndDecrement() methods in AtomicInteger. Their implementation principles are exactly the same as the above two methods. The difference is that the return value is different. The first two methods return the changed value, that is, next. And these two methods return the value before the change, that is, current. There are many other methods, which are not listed here.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326260620&siteId=291194637