Optimistic Locking - CAS

 

As we all know, Java is multi-threaded. However, Java's support for multithreading is actually a double-edged sword. Once it involves multiple threads operating on shared resources, it may cause thread safety issues if not handled properly. Thread safety can be very complex, and the order in which operations are performed in multiple threads is unpredictable without adequate synchronization.

The main way of multi-threaded communication in Java is the way of shared memory. There are two main concerns of shared memory: visibility and ordering. Coupled with the atomicity of compound operations, we can think that Java's thread safety issues have three main concerns: visibility, ordering, and atomicity.

The Java Memory Model (JMM) solves the problem of visibility and ordering, while the lock solves the problem of atomicity. Other related knowledge of JMM and locks will not be introduced in detail here. But we have to discuss a question, that is, are locks beneficial or not?

lock problem

Before JDK1.5, Java used synchronizedkeywords to ensure synchronization. By using a consistent locking protocol to coordinate access to shared state, it can ensure that no matter which thread holds the lock of the shared variable, it can be accessed in an exclusive way. these variables. An exclusive lock is actually a pessimistic lock, so it can be said synchronizedto be a pessimistic lock.

The pessimistic locking mechanism has the following problems:

Under multi-threaded competition, locking and releasing locks will cause more context switching and scheduling delays, causing performance problems.

A thread holding a lock causes all other threads that need the lock to hang.

If a high-priority thread waits for a low-priority thread to release the lock, it will cause a priority inversion, causing a performance risk.

Another more efficient lock is optimistic locking. The so-called optimistic locking is to complete an operation without locking each time but assuming that there is no conflict. If it fails due to a conflict, it will retry until it succeeds.

Variables are a more lightweight synchronization mechanism than locks volatilebecause operations like context switching and thread scheduling don't happen when using these variables, but they volatiledon't solve the atomicity problem, so when a variable depends on an old value it can't Use volatilevariables. Therefore, for synchronization, it is necessary to return to the locking mechanism in the end.

optimistic locking

Optimistic locking (  Optimistic Locking) is actually an idea. Compared with pessimistic locking, optimistic locking assumes that data will not cause conflicts in general, so when the data is submitted for update, the data conflict will be formally detected. If a conflict is found, it will return the user error. information, allowing users to decide what to do.

The concept of optimistic locking mentioned above has actually explained its specific implementation details: there are mainly two steps: conflict detection and data update. A typical implementation method is Compare and Swap( CAS).

CASE

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.

A CAS operation consists of three operands - the memory location (V), the expected old value (A), and the new value (B). If the value of the memory location matches the expected original value, the processor automatically updates the location value to the new value. Otherwise, the processor does nothing. In either case, it returns the value at that location before the CAS instruction. (Some special cases of CAS will only return whether CAS succeeded, not extract the current value.) CAS effectively says "I think position V should contain value A; if it does, put B in this position; Otherwise, don't change the position, just tell me the current value of this position." This is actually the same as the conflict check + data update principle of optimistic locking.

Here again, optimistic locking is an idea. CAS is an implementation of this idea.

Java support for CAS

New in JDK1.5 java.util.concurrent(JUC) is based on CAS. Relative to synchronizedthis blocking algorithm, CAS is a common implementation of a non-blocking algorithm. So JUC has a great improvement in performance.

Let's take java.util.concurrentthe AtomicIntegerexample in and see how thread safety is guaranteed without using locks. Mainly understand getAndIncrementthe method, the role of the method is equivalent to the  ++i operation.

public class AtomicInteger extends Number implements java.io.Serializable {  

        private volatile int value;  

    public final int get() {  
        return value;  
    }  

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

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

In the absence of a lock mechanism, the field value needs to use the volatile primitive to ensure that the data between threads is visible. In this way, you can directly read the value of the variable when you get it. Then let's see ++ihow it is done.

getAndIncrementThe CAS operation is used, each time the data is read from the memory and then the CAS operation is performed on the data and +1the result, and the result is returned if it is successful, otherwise it is retried until it is successful. And compareAndSetuse JNI to complete the operation of CPU instructions.

ABA problem

CAS can cause the "ABA problem".

An important prerequisite for the realization of the CAS algorithm is to take out the data in the memory at a certain time, and compare and replace it at the next time, then the time difference will cause the data to change.

For example, a thread one fetches A from the memory location V, at this time another thread two also fetches A from the memory, and two performs some operations to become B, and then two changes the data at the V location to A. This is When the thread one performs the CAS operation and finds that there is still A in the memory, then the one operation succeeds. Although the CAS operation of thread one is successful, it does not mean that the process is without problems.

Part of the implementation of optimistic locking is to solve the ABA problem by means of version number ( version). The optimistic lock will carry a version number every time it performs a data modification operation. Once the version number and the data version number are consistent, the modification can be performed. Action and perform the action on the version number +1, otherwise it fails. Because the version number will increase with each operation, there will be no ABA problem, because the version number will only increase and not decrease.

Summarize

The issue of thread safety in Java is very important. To ensure thread safety, a locking mechanism is required. There are two locking mechanisms: optimistic locking and pessimistic locking. Pessimistic locks are exclusive locks, blocking locks. Optimistic locks are non-exclusive locks, non-blocking locks. java.util.concurrentOne implementation of optimistic locking is CAS, which is widely used in JDK 1.5 . But it is worth noting that this algorithm will have the ABA problem.

CAS and object creation

In addition, CAS has another application, that is, in the process of creating objects in the JVM. Object creation is very frequent in virtual machines. Even just modifying the location pointed to by a pointer is not thread-safe under concurrent conditions. It may be that memory space is being allocated to object A, the pointer has not had time to be modified, and object B also uses the original pointer to allocate memory. . There are two solutions to this problem, one of which is to use CAS coupled with failed retry to ensure the atomicity of update operations.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326605079&siteId=291194637