Principles of AQS and CAS

Locking mechanism (AQS and CAS)

一、AQS

  1. Principle of AQS

    AQS: AbstractQuenedSynchronizer abstract queue synchronizer. It is a locking mechanism other than the synchronized keyword that comes with java.
    The full name of AQS (AbstractQueuedSynchronizer), this class is in the java.util.concurrent.locks package

  2. The core idea of ​​AQS

    If the requested shared resource is idle, the thread currently requesting the resource is set to a valid worker thread, and the shared resource is set to a locked state. If the requested shared resource is occupied, a set of threads is required to block waiting and be The mechanism of lock allocation at wake-up. This mechanism AQS is implemented with CLH queue locks, that is, threads that cannot temporarily acquire locks are added to the queue.
    A CLH (Craig, Landin, and Hagersten) queue is a virtual two-way queue. A virtual two-way queue does not have a queue instance, but only has an association relationship between nodes.
AQS encapsulates each thread requesting shared resources into a node (Node) of a CLH lock queue to realize lock allocation.

    AQS is based on the CLH queue, and uses volatile to modify the shared variable state. The thread changes the state symbol through CAS. If it succeeds, it acquires the lock successfully. If it fails, it enters the waiting queue and waits to be awakened.

    Note: AQS is a spin lock: when waiting to wake up, spin (while(!cas())) is often used to keep trying to acquire the lock until it is successfully acquired by other threads

    The locks that implement AQS are: spin locks, mutex locks, read locks, write locks, conditional yields, semaphores, and barriers are all derivatives of
AQS. The specific ways of AQS implementation are as follows:

  

  3. The bottom layer of AQS uses the template method pattern

  The design of the synchronizer is based on the template method pattern. If you need to customize the synchronizer, the general way is as follows (the template method pattern is a classic application):

    The consumer inherits AbstractQueuedSynchronizer and overrides the specified method. (These overriding methods are very simple, nothing more than the acquisition and release of shared resource state)

    Combine AQS in the implementation of a custom sync component and call its template methods, which call methods overridden by the consumer. This is very different from the way we used to implement interfaces. This is a classic application of the template method pattern.

    When implementing a custom synchronizer, you only need to implement the acquisition and release methods of the shared resource state. As for the maintenance of the specific thread waiting queue, AQS has been implemented at the top level. When implementing a custom synchronizer, it mainly implements the following methods:  
      (1) isHeldExclusively(): Whether the thread is monopolizing resources. You only need to implement it if you use a condition.
      (2) tryAcquire(int): Exclusive mode. Attempt to get the resource, return true on success, false on failure.
      (3) tryRelease(int): Exclusive mode. Attempt to release the resource, return true on success, false on failure.
      (4) tryAcquireShared(int): Shared method. Try to get the resource. A negative number means failure; 0 means success, but no remaining available resources; a positive number means success, and there are remaining resources.
      (5) tryReleaseShared(int): sharing method. Attempt to release the resource, return true if subsequent waiting nodes are allowed to wake up after release, false otherwise.

    Take ReentrantLock as an example, (reentrant exclusive lock): state is initialized to 0, which means unlocked state. When thread A lock(), it will call tryAcquire() to take the exclusive lock and state+1. After that, other threads will try to acquire tryAcquire. It will fail until the A thread unlocks () to state=0, and other threads have no chance to acquire the lock. Before A releases the lock, he can repeatedly acquire the lock (state accumulation), which is the concept of reentrancy.

    Note: As many locks as you acquire, you must release the locks to ensure that the state can return to zero.

    Taking CountDownLatch as an example, the task is divided into N sub-threads to execute, the state is initialized to N, and the N threads are executed in parallel. After each thread is executed, countDown() once, and the state will decrease CAS by one. When all N sub-threads are executed, state=0, the main calling thread will be unpark(), and the main calling thread will return from the await() function to continue the subsequent actions.

    Generally speaking, custom synchronizers are either exclusive methods or shared methods, and they only need to implement one of tryAcquire-tryRelease and tryAcquireShared-tryReleaseShared. But AQS also supports custom synchronizers to implement both exclusive and shared methods, such as ReentrantReadWriteLock.

    In the two methods of acquire() acquireShared(), the thread ignores interrupts in the waiting queue, and acquireInterruptibly()/acquireSharedInterruptibly() supports responding to interrupts.

2. CAS

  1. What is CAS 

    CAS: Compare and Swap, that is, compare and exchange;

    jdk1.5 adds the concurrent package java.util.concurrent.*, and the following classes use the CAS algorithm to implement an optimistic lock that is different from the synchronized synchronization lock. Before jdk1.5, the java language used the synchronized keyword to ensure synchronization, which is an exclusive lock and a pessimistic lock;

  2, CAS algorithm understanding

    2.1 Compared with locks

      Using a comparison swap makes the program look a little more complicated. However, due to its non-blocking nature, it is inherently immune to deadlock problems, and the interaction between threads is far less than the lock-based method; more importantly, the lock-free method is completely free from lock competition. There is no system overhead, and there is no overhead caused by frequent scheduling between threads; therefore, it has better performance than the lock-based method;

    2.2 The benefits of lock-free:

      2.2.1 In the case of high concurrency, it has better performance than programs with locks;

      2.2.2 It is inherently deadlock immune;

    2.3 The process of the CAS algorithm:

      It contains three parameters CAS(V,E,N): V represents the updated variable, E represents the expected value, and N represents the new value;    

      2.3.1 When a thread accesses, first synchronize the data in the main memory to the working memory of the
      thread 2.3.2 Assuming that both thread A and thread B have changed the data, then if thread A first obtains the execution permission
      2.3.3 Thread A will first compare whether the data in the working memory is consistent with the data in the main memory. If it is consistent (V==E), it will be updated. If it is inconsistent, the data will be refreshed.
      2.3.4 After the update is completed, thread B will The data should also be updated. The main memory data and the working memory data are compared. If they are consistent, update the data. If they are inconsistent, update the main memory data to the working memory again. Then compare the data in the two memories again until they are consistent.

      

    2.4 CAS operations are carried out with an optimistic attitude

      It always thinks that it can successfully complete the operation; when multiple threads use CAS to operate a variable at the same time, only one will win and update successfully, and the rest will fail; the failed thread will not be suspended, just be told that it failed , and it is allowed to try again, and of course, the failed thread is allowed to give up the operation; based on this principle, even if there is no lock in the CAS operation, it can find the interference of other threads on the current thread and handle it appropriately;

    2.5 Briefly

      CAS requires you to give an additional expected value, which is what you think the variable should look like now; if the variable is not what you imagined, it means that it has been modified by someone else; you have to read it again and try to modify it again Enough;

    2.6 At the hardware level

      Most modern processors already support atomic CAS instructions; after jdk1.5, virtual machines can use this instruction to implement concurrent operations and concurrent data structures, and this operation can be said to be non-existent in virtual machines. everywhere;

  3. Disadvantages of CAS

    There is an obvious problem with CAS, that is, the ABA problem;

    Question: If the variable V is A when it is first read, and when it is ready to be assigned, it is checked that it is still A, does that mean that its value has not been modified by other threads?

    If it was changed to B during this time, and then changed back to A, then the CAS operation would mis-task it was never changed. In this case, the java concurrent package provides a marked atomic application class AtomicStampedRefernce, which can ensure the correctness of CAS through the version of the variable value;

  4. Atomic class

    Atomic classes in java can be roughly divided into four categories:

      Atomic update primitive types;

      Atomic update array type;

      Atomic update reference type;

      Atomic update property type;

    The concept of lock-free is used in these atomic classes, and thread-safe types of CAS operations are directly used in some places.

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerTest implements Runnable {

    private static Integer count=1;
    private static AtomicInteger atomicInteger=new AtomicInteger();

    @Override
    public void run() {
        while (true){
            int count=getCountAtomic();
            System.out.println(count);
            if (count>=150){
                break;
            }
        }
    }

    public synchronized Integer getCount(){
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        return count++;
    }

    public Integer getCountAtomic(){
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        return atomicInteger.incrementAndGet();
    }

    public static void main(String[] args){
        AtomicIntegerTest test = new AtomicIntegerTest();
        Thread thread1 = new Thread(test);
        Thread thread2 = new Thread(test);
        thread1.start();
        thread2.start();
    }
}

Guess you like

Origin blog.csdn.net/qq_35222843/article/details/114062071
AQS
AQS