AQS design ideas and detailed explanation of important fields

This article is based on JDK1.8

The learning objectives of this article

Understand the design idea of ​​AQS and the meaning of important fields, such as expressing the synchronization state through the state field.

Understand the structure of AQS internal maintenance chain two-way synchronization queue and several important pointers.

Understand the five important synchronization states.

Identify two modes: shared mode and exclusive mode.

Learn the template methods provided by AQS in the two modes: obtaining and releasing synchronization state related methods.

Understand AQS's support for condition variables such as Condition and ConditionObject.

In-depth understanding of the mechanism of waiting for notification through the case of Condition.

AQS overview

AQS is AbstractQueuedSynchronizer, queue synchronizer. It is the basic framework for building many synchronization components, such as ReentrantLock, ReentrantReadWriteLock, etc., and is the core basic component of JUC concurrent packages.

The AQS framework is constructed based on the template method design pattern, and subclasses inherit it and implement the abstract methods it provides to manage the synchronization state.

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	// 序列化版本号
    private static final long serialVersionUID = 7373984972572414691L;

    /**
     * 创建AQS实例,初始化state为0,为子类提供
     */
    protected AbstractQueuedSynchronizer() { }
    
    /*----------------  同步队列构成 ---------------*/

    // 等待队列节点类型
    static final class Node {
        //...省略
    }

    /**
     * 除了初始化之外,它只能通过setHead方法进行修改。注意:如果head存在,它的waitStatus保证不会被取消
     */
    private transient volatile Node head;

    /**
     * 等待队列的尾部,懒初始化,之后只在enq方法加入新节点时修改
     */
    private transient volatile Node tail;
    
    /*----------------  同步状态相关 ---------------*/

    /**
     * volatile修饰, 标识同步状态,state为0表示锁空闲,state>0表示锁被持有,可以大于1,表示被重入
     */
    private volatile int state;

    /**
     * 返回当前同步状态
     */
    protected final int getState() {
        return state;
    }

    /**
     * 设置同步状态
     */
    protected final void setState(int newState) {
        state = newState;
    }

    /**
     * 利用CAS操作更新state值
     */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

    static final long spinForTimeoutThreshold = 1000L;
    
    // 这部分和CAS有关 
    // 获取Unsafe实例
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // 记录state在AQS类中的偏移值
    private static final long stateOffset;
    static {
        try {
            // 初始化state变量的偏移值
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
        } catch (Exception ex) { throw new Error(ex); }
    }
}

The above includes the basic fields of AQS, and the core is two parts:

The node type Node of the internal FIFO queue, and the head and tail pointer field.

Methods related to synchronization status, settings, acquisition, CAS update, etc.

Next we will learn these contents one by one.

AbstractOwnableSynchronizer

AbstractQueuedSynchronizer inherits from AbstractOwnableSynchronizer, which provides the function of setting or acquiring the owner thread of the exclusive lock .

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {
 
    private static final long serialVersionUID = 3737899427754241961L;
 	// 本身是抽象类,该构造方法实为为子类提供
    protected AbstractOwnableSynchronizer() { }
 
	/* 互斥模式同步下的当前线程 */
    private transient Thread exclusiveOwnerThread;
 
	/* 设置当前拥有独占访问的线程。锁的拥有线程,null 参数表示没有线程拥有访问。
     * 此方法不另外施加任何同步或 volatile 字段访问。
     */
    protected final void setExclusiveOwnerThread(Thread t) {
        exclusiveOwnerThread = t;
    }
 
	/* 返回由 setExclusiveOwnerThread 最后设置的线程;
     * 如果从未设置,则返回 null。
     * 此方法不另外施加任何同步或 volatile 字段访问。 
     */
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }
}

Here the exclusiveOwnerThread field is used to determine whether the current thread holds the lock, because the lock can be reentered, so the following pseudo code will be generated:

if (currThread == getExclusiveOwnerThread()) {
	state++;
}

Synchronize queue and Node node

tips: The synchronous queue is called the CLH queue, which is the collective name of Craig, Landin, and Hagersten.

AQS uses the built-in FIFO to synchronize the two-way queue to complete the queuing of the resource acquisition thread, internally through the node head [actually a virtual node, the real first thread is in the position of head.next] and tail to record the head and tail elements of the queue , The queue element type is Node.

The structure of the CLU synchronization queue is as follows, and the specific operations will be summarized after:

Source: [AQS] core implementation

 

 

If the current thread fails to acquire the synchronization state (lock), AQS will construct a node (Node) and add it to the synchronization queue, and block the current thread at the same time.

When the synchronization state is released, the thread in the node will be awakened to make it try to obtain the synchronization state again.

/**
     * 等待队列中的节点类
     */
    static final class Node {
        /** 标识共享式节点 */
        static final Node SHARED = new Node();
        /** 标识独占式节点 */
        static final Node EXCLUSIVE = null;

        /** ------------ 等待状态 ---------------*/
        
        /** 表示该线程放弃对锁的争夺 */
        static final int CANCELLED =  1;
        /** 当前node的后继节点对应的线程需要被唤醒 */
        static final int SIGNAL    = -1;
        /** 线程在条件队列中等待 */
        static final int CONDITION = -2;
        /** 释放共享资源时需要通知其他节点 */
        static final int PROPAGATE = -3;
        
        /** waitStatus == 0 表示不是以上任何一种 */
        
        // 记录当前线程的等待状态,以上五种
        volatile int waitStatus;

        // 前驱节点
        volatile Node prev;

        // 后继节点
        volatile Node next;

        // node存储的线程
        volatile Thread thread;
		
        // 当前节点在Condition中等待队列上的下一个节点
        Node nextWaiter;

        // 判断是否为共享式获取同步状态
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * 为什么不直接判断prev,而是用p变量判断呢?
         * 避免并发的情况下,prev判断完为null,恰好被修改
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
		// 用于SHARED的创建
        Node() {    
        }
		// 用于addWaiter(Node mode)方法,指定模式的
        Node(Thread thread, Node mode) {     
            this.nextWaiter = mode;
            this.thread = thread;
        }
		// 用于addConditionWaiter()方法
        Node(Thread thread, int waitStatus) { 
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

The value and representation of each waiting state have been marked in the comments, here is a summary:

The wait status is represented by the waitStatus field, which is used to control the blocking and wake-up of the thread. In addition to the four types described above, there is actually a status of 0, which is already clear in the comments of the source code.

[CANCELLED = 1] Canceled state, due to timeout or interruption, the node will be set to canceled state, and it will remain in the canceled state, and will not participate in the competition. If waitStatus>0, it can be considered that the thread has cancelled the wait.

[SIGNAL = -1] The thread of the successor node is in a waiting state. If the thread of the current node releases the synchronization state or is cancelled, the successor node will be notified so that the thread of the successor node can run.

[CONDITION = -2] The node is in the waiting queue, and the node thread is waiting on the Condition. When other threads call signal() on the Condition, the node will transfer from the waiting queue to the synchronization queue and join the synchronization state. Acquiring.

[PROPAGATE = -3] means that the next shared synchronization status acquisition will be unconditionally propagated.

0: Initial state, none of the above.

Regarding the synchronization queue maintained internally by AQS, here is just to understand some basic concepts, and follow-up to learn more about the attention points of queue operation.

Synchronization state

AQS uses an int type member variable state to represent the synchronization state. It is modified with volatile and provides three thread-safe methods about state:

getState(), read the synchronization state.

setState(int newState), set the synchronization state to newState.

compareAndSetState(int expect, int update), CAS operation updates the state value.

A state of 0 indicates that the lock is free, state>0 indicates that the lock is held, and can be greater than 1, indicating that it is reentered. Different subclass implementations have slightly different meanings of state. Let me give a few examples:

ReentrantLock: state represents the reentrant number of times the current thread acquires the lock.

ReetrantReadWriteLock: The high 16 bits of state indicate the read state, that is, the number of times the read lock is acquired, and the low 16 bits indicate the number of reentrant threads that have acquired the write lock.

semaphore: state represents the number of signals currently available.

CountDownlatch: state represents the current value of the counter.

Analysis of important methods

For AQS, the key to thread synchronization is to operate state. According to whether the state belongs to a thread, the state of operation is divided into exclusive mode and shared mode.

Exclusive acquisition and release of synchronization status

The resource acquired in an exclusive way is bound to a specific thread. If a thread acquires the resource, it will mark that the thread has acquired it. When other threads try to manipulate the state to acquire the resource again, they will find that the resource is not currently held by themselves. , It will block after the acquisition fails.

For example, the implementation of exclusive lock ReentrantLock, when a thread acquires the ReentrantLock lock, in AQS, it will first use the CAS operation to change the state value from 0 to 1, and then set the current lock holder as the current thread. When the lock is acquired again, it is found that it is the holder of the lock, and the state value is changed from 1 to 2, that is, the number of reentrants is set, and when another thread acquires the lock, it finds that it is not the holder of the lock Will be put into the AQS blocking queue and then hang.

// 独占式获取同步状态,成功后,其他线程需要等待该线程释放同步状态才能获取同步状态
    public final void acquire(int arg) {
        // 首先调用 tryAcquire【需要子类实现】尝试获取资源,本质就是设置state的值,获取成功就直接返回
        if (!tryAcquire(arg) &&
            // 获取失败,就将当前线程封装成类型为Node.EXCLUSIVE的Node节点,并插入AQS阻塞队列尾部
            // 然后通过自旋获取同步状态
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

	// 与 acquire(int arg) 相同,但是该方法响应中断。
	// 如果其他线程调用了当前线程的interrupt()方法,响应中断,抛出异常。
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        // interrupted()方法将会获取当前线程的中断标志并重置
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
	//尝试获取锁,如果获取失败会将当前线程挂起指定时间,时间到了之后当前线程被激活,如果还是没有获取到锁,就返回false。
	//另外,该方法会对中断进行的响应,如果其他线程调用了当前线程的interrupt()方法,响应中断,抛出异常。
    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
	// 独占式释放同步状态
    public final boolean release(int arg) {
        // 尝试使用tryRelease释放资源,本质也是设置state的值
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                // LockSupport.unpark(thread) 激活AQS里面被阻塞的一个线程
                // 被激活的线程则使用tryAcquire 尝试,看当前状态变量state的值是否能满足自己的需要,
                //满足则该线程被激活,然后继续向下运行,否则还是会被放入AQS队列并被挂起。
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

It should be noted that the tryRelease and tryAcquire methods are not implemented in AQS. The task of implementation is handed over to specific subclasses. The subclasses are implemented according to specific scenario requirements. The value of state is set and modified through the CAS algorithm.

Shared acquisition and release of synchronization status

The resource corresponding to the shared mode is not related to the specific thread. When multiple threads request resources, they compete to obtain resources through CAS. When one thread obtains the resource, another thread obtains it again if the current resource can still satisfy it. If required, the current thread only needs to use the CAS method to obtain it.

For example, Semaphore semaphore, when a thread acquires a semaphore through the acquire() method, it will first see whether the current number of semaphores meets the needs, if not, put the current thread into the blocking queue, and if it meets, get the signal through spin CAS the amount.

//共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,
	// 与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态;
	public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            // 尝试获取资源,如果成功则直接返回
            // 如果失败,则将当前线程封装为类型为Node.SHARED的Node节点并插入AQS阻塞队列尾部
            // 并使用LockSupport.park(this)挂起自己
            doAcquireShared(arg);
    }
	// 共享式获取同步状态,响应中断
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }
	//共享式获取同步状态,增加超时限制
    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
    }
	//共享式释放同步状态
    public final boolean releaseShared(int arg) {
        // 尝试释放资源
        if (tryReleaseShared(arg)) {
            // 调用LockSupport.unpark(thread)激活AQS队列里被阻塞的一个线程。
            // 被激活的线程使用tryReleaseShared查看当前状态变量state是否能满足自己的需要。
            // 如果满足需要,则线程被激活继续向下运行,否则还是放入AQS队列并被挂起
            doReleaseShared();
            return true;
        }
        return false;
    }

The Interruptibly method means that it needs to respond to interruption. When a thread calls a method with the Interruptibly keyword to obtain a resource or fails to obtain a resource, it is suspended. If other threads interrupt the thread, the thread will throw an InterruptedException and return.

AQS condition variable support

Condition interface

Contition is a conditional queue in a broad sense. It uses await() and signal() to provide threads with a more flexible wait/notify mode .

Condition must be used with Lock, because access to shared state variables occurs in a multithreaded environment. An instance of Condition must be bound to a Lock, so the call of await and signal must be between lock and unlock, and the condition can be used only after the lock. Take ReentrantLock as an example, simply use it as follows:

public class ConditionTest {

    public static void main(String[] args) {
        final ReentrantLock lock = new ReentrantLock();
        final Condition condition = lock.newCondition();

        Thread thread1 = new Thread(() -> {
            String name = Thread.currentThread().getName();

            lock.lock();
            System.out.println(name + " <==成功获取到锁" + lock);
            try {
                System.out.println(name + " <==进入条件队列等待");
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + " <==醒了");
            lock.unlock();
            System.out.println(name + " <==释放锁");
        }, "等待线程");

        thread1.start();

        Thread thread2 = new Thread(() -> {
            String name = Thread.currentThread().getName();

            lock.lock();
            System.out.println(name + " ==>成功获取到锁" + lock);
            try {
                System.out.println("========== 这里演示await中的线程没有被signal的时候会一直等着 ===========");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + " ==>通知等待队列的线程");
            condition.signal();
            lock.unlock();
            System.out.println(name + " ==>释放锁");
        }, "通知线程");

        thread2.start();
    }
}等待线程 <==成功获取到锁java.util.concurrent.locks.ReentrantLock@3642cea8[Locked by thread 等待线程]
等待线程 <==进入条件队列等待
通知线程 ==>成功获取到锁java.util.concurrent.locks.ReentrantLock@3642cea8[Locked by thread 通知线程]
========== 这里演示await中的线程没有被signal的时候会一直等着 ===========
通知线程 ==>通知等待队列的线程
通知线程 ==>释放锁
等待线程 <==醒了
等待线程  <==释放锁

ConditionObject inner class

The relationship between AQS, Lock, Condition, ConditionObject:

ConditionObject is an internal class of AQS, which implements the Condition interface. The newCondition() method is provided in Lock, which is entrusted to the internal AQS implementation Sync to create ConditionObject objects and enjoy AQS's support for Condition.

// ReentrantLock#newCondition
	public Condition newCondition() {
        return sync.newCondition();
    }
	// Sync#newCondition
    final ConditionObject newCondition() {
        // 返回Contition的实现,定义在AQS中
        return new ConditionObject();
    }

ConditionObject is used to implement thread synchronization in combination with locks. ConditionObject can directly access variables inside the AQS object, such as the state value and the AQS queue . ConditionObject is a condition variable, each condition variable corresponds to a condition queue (one-way linked list queue), which is used to store the thread that is blocked after calling the await method of the condition variable. ConditionObject maintains the first and last nodes:

public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    /** First node of condition queue. */
    private transient Node firstWaiter;
    /** Last node of condition queue. */
    private transient Node lastWaiter;
}

Seeing this, we need to make it clear that the conditional queue here is different from the synchronization queue analyzed above:

AQS maintains a queue that is currently waiting for resources, and Condition maintains a queue that is waiting for signal signals.

Each thread will exist in one of the above two queues, lock and unlock correspond to the AQS queue, signal and await correspond to the condition queue, and the thread node repeatedly jumps between them.

Here we analyze the above demo for a better understanding:

To simplify, I will use D to represent the waiting thread and T to represent the notification thread.

  1. [D] First call the lock.lock() method, there is no competition at this time, and [D] is added to the AQS waiting queue.
  2. [D] Call the condition.await() method, at this time [D] is removed from the AQS waiting queue and added to the condition waiting queue corresponding to the condition.
  3. [D] After falling into waiting, [T] starts. Since [D] in the AQS queue has been removed, [T] also quickly acquires the lock. Correspondingly, [T] is also added to the AQS waiting queue in.
  4. [T] Then call the condition.signal() method. At this time, there is only one node [D] in the condition queue corresponding to condition, so [D] is taken out and added to the waiting queue of AQS again. At this time [D] was not awakened, but simply changed positions.
  5. Then [T] execute lock.unlock(), after releasing the lock, it will wake up [D] in the AQS queue, at this time [D] is really woken up and executed.

You see, the [D] thread is indeed jumping repeatedly in the two queues. The content of the Condition is just an introduction. Later, I will make a detailed study summary. If you are interested in this series, you can pay attention to it. The follow-up will continue. In-depth study of concurrency.

Original link: https://www.cnblogs.com/summerday152/p/14238284.html

If you think this article is helpful to you, you can follow my official account and reply to the keyword [Interview] to get a compilation of Java core knowledge points and an interview gift package! There are more technical dry goods articles and related materials to share, let everyone learn and progress together!

Guess you like

Origin blog.csdn.net/weixin_48182198/article/details/112341405