CAS and AQS of java concurrency

CAS(Compare And Swap)

CASE

Comparing and exchanging is a solution to the performance loss caused by the use of locks under multithreading. The CAS operation contains three operands-V (memory value) A (expected original value) B (new value-updated Value), if the memory value V = A expected original value, which means that the value has not been modified, the value will be updated to B, otherwise no processing will be done; the latest value will be returned;

The Unsafe class used in Java provides a hardware-level atomic operation to implement CAS,

Use of CAS in AtomicInteger

// 返回旧值,并设置新值, 这里是调用的 unsafe的getAndSetInt方法
   public final int getAndSet(int newValue) {
    
    
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
    ...
 //  看下unsafe 的方法
    public final int getAndSetInt(Object var1, long var2, int var4) {
    
    
        int var5;
        do {
    
    
            var5 = this.getIntVolatile(var1, var2);
            // 这里 使用了 循环 进行 CAS操作
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));

        return var5;
    }
   ...
   //通过cas原子性的将值设置为更新值,也是使用了 unsafe中的方法
    public final boolean compareAndSet(int expect, int update) {
    
    
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

Under normal circumstances, when the thread competition is not very fierce, CAS is more efficient than the locking method, but if the loop does not jump out or cannot be set successfully for a long time, it will waste CPU

ABA problem

There is an ABA problem when using CAS, that is, the value in the memory has been changed but it has been changed back. When the CAS operation is performed, the value of the memory is the same as the expected value, but it is actually the value changed back after the modification; It is the ABA problem; the solution is to add a version number.
E.g

A (memory value)-B (expected value)-C (new value)
becomes A1-B1 -C1, so the operation is to compare when A is equal to B and compare the version numbers for consistency. If the version numbers are consistent, update to C and add A version number,

AQS(AbstractQueuedSynchronizer)

AQS is an internal framework provided by the JDK for implementing blocking locks and related synchronizers based on FIFO waiting queues

Provide a framework for implementing blocking locks and related synchronizers (semaphores, events, etc.) that rely on first-in-first-out (FIFO) waiting queues. This class is designed as a useful basis for most types of synchronizers, which rely on a single atomic int value to represent state . The subclass must define a protected method to change this state, and define the meaning of the state based on whether the object is acquired or released. Given these, the other methods in this class perform all queuing and blocking mechanisms. Subclasses can keep other state fields, but only atomically update int using methods to manipulate values ​​getState(), setState(int) and compareAndSetState(int, int) are tracked relative to synchronization.
The subclass should be defined as a non-public internal helper class to implement the synchronization properties of its closed class. The AbstractQueuedSynchronizer class does not implement any synchronization interface. Instead, it defines some methods, such as acquireInterruptibly(int), which can be invoked to perform its public methods appropriately through specific locks and related synchronizers.
This class supports the default exclusive mode and shared mode. When acquiring in exclusive mode, attempts to acquire through other threads cannot succeed. The shared mode acquired by multiple threads may (but need not) succeed. Except in a mechanical sense, this class does not understand these differences. When the shared mode is successfully acquired, the next waiting thread (if it exists) must also determine whether it can also be acquired. Threads waiting in different modes share the same FIFO queue. Usually, implementation subclasses only support one of these modes, but both can play a role in ReadWriteLock. Subclasses that only support exclusive or shared only modes do not need to define methods that support unused modes.
The nested AbstractQueuedSynchronizer.ConditionObject defined by this class can be used as a type of Condition supported by subclasses to support the exclusive mode for the implementation of the method isHeldExclusively() Report whether it is synchronized and exclusive relative to staying in the current thread, the method release(int) and the current call The getState() value completely releases this purpose, and acquire(int), given this saved state value, finally restores this object to its previously acquired state. The AbstractQueuedSynchronizer method will create such a condition, so if this constraint cannot be met, do not use it. The behavior of AbstractQueuedSynchronizer.ConditionObject of course depends on the semantics of its synchronizer implementation.
This class provides inspection, detection, and monitoring methods for internal queues, as well as similar methods for conditional objects. These can be exported to the class as needed, using AbstractQueuedSynchronizer for synchronization mechanism.
This type of serialization only stores the underlying atomic integer to maintain state, so the deserialized object has an empty thread queue. A typical subclass that needs to be serializable will define a readObject method that can restore it to the known initial state at the time of readObject.

The state state
AbstractQueuedSynchronizer maintains a volatile modified int variable state to represent the synchronization state. Volatile guarantees visibility in a state multithreaded environment. And operations on state are all atomic

   /**
     * The synchronization state.
     */
    private volatile int state;
    
    protected final int getState() {
    
    
        return state;
    }
    protected final void setState(int newState) {
    
    
        state = newState;
    }
    protected final boolean compareAndSetState(int expect, int update) {
    
    
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

AQS's custom sharing method

AQS is divided into exclusive and shared resource sharing methods:
Exclusive (exclusive, only one thread can execute, such as ReentrantLock) and Share (shared, multiple threads can execute at the same time, such as Semaphore/CountDownLatch)
The custom synchronizer is only implemented when it is implemented It is only necessary to realize the acquisition and release of shared resource state. As for the maintenance of the specific thread waiting queue (such as the failure to acquire resources into the queue/waking up, etc.), AQS has been implemented at the top level. The following methods are mainly implemented when implementing a custom synchronizer:

isHeldExclusively() : Whether the thread is monopolizing resources. Only use condition to realize it.
tryAcquire(int) : Exclusive mode. Attempt to acquire resources, return true if successful, false if failed.
tryRelease(int) : Exclusive mode. Attempt to release the resource, return true if successful, false if failed.
tryAcquireShared(int) : The sharing method. Try to obtain resources. A negative number means failure; 0 means success, but there are no remaining resources available; a positive number means success and there are remaining resources.
tryReleaseShared(int) : The sharing method. Try to release the resource, and return true if it is allowed to wake up subsequent waiting nodes after release, otherwise return false.

Source code
1. acquire(int)
acquire is a way to acquire resources exclusively. If the resources are acquired, the thread returns to run directly, otherwise it enters the waiting queue and is blocked, and the thread may be repeatedly blocked and unblocked; the whole process ignores thread interruption Impact: This method is the top-level entry of the thread to obtain the shared resource in the exclusive mode. After the resource is obtained, the thread can execute its critical section code;

    public final void acquire(int arg) {
    
    
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcquire(arg) tries to acquire resources, and returns if successful; this is the
addWaiter() that needs to be implemented by yourself to add the thread to the end of the waiting queue and mark it as exclusive mode
acquireQueued() to make the thread acquire resources in the waiting queue, always Return only after obtaining the resource. If it has been interrupted during the whole waiting process, it returns true, otherwise it returns false.
selfInterrupt(), add the interrupt

Main logic: By calling the tryAcquire method implemented by the subclass, this method guarantees the thread-safe acquisition of the synchronization state. If the synchronization state acquisition fails, call addWaiter() to add the thread to the end of the synchronization queue in exclusive mode, and finally call acquireQueued() method,

Guess you like

Origin blog.csdn.net/xiaodujava/article/details/101526074