Loading

AbstractQueuedSynchronizer 理解

AQS 成员变量

    /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;	//头节点

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;	//尾节点

    /**
     * The synchronization state.
     * 同步状态,用于展示当前临界资源的获锁情况
     * 0代表没有被占用,大于0代表有线程持有当前锁
     * 这个值可以大于1,是因为锁可以重入,每次重入都加上 1
     */
    private volatile int state;

线程获取锁的两种模式:

通过修改State字段表示的同步状态来实现多线程的独占模式和共享模式

  • 独占模式:一旦被占用,其他线程都不能占用
  • 共享模式:一旦被占用,其他共享模式下的线程才能占用

头节点和尾节点的作用:

当一个线程在某一时刻没有获取到共享资源,它可以选择进行排队。

这个队列是一个FIFO先进先出的双向链表,head 和 tail 分别表示队列的头和尾

           +------+  prev +-----+       +-----+
      head |      | <---- |     | <---- |     |  tail
           +------+       +-----+       +-----+

Node 成员变量

static final class Node {
  
        /** Marker to indicate a node is waiting in shared mode */
  			//表示线程以共享的模式等待锁
        static final Node SHARED = new Node();
  
        /** Marker to indicate a node is waiting in exclusive mode */
  			//表示线程正在以独占的方式等待锁
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
  			//表示线程获取锁的请求已经取消了
        static final int CANCELLED =  1;
  
        /** waitStatus value to indicate successor's thread needs unparking */
  			//表示线程已经准备好了,就等资源释放了
        static final int SIGNAL    = -1;
  
        /** waitStatus value to indicate thread is waiting on condition */
  			//表示节点在等待队列中,节点线程等待唤醒
        static final int CONDITION = -2;
  
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
  			//当前线程处在SHARED情况下,该字段才会使用
        static final int PROPAGATE = -3;

        volatile int waitStatus;	//当前节点在队列中的状态

        volatile Node prev; //前指针

        volatile Node next;	//后指针

        volatile Thread thread;  //处于该节点的线程对象

        //...
    }

独占模式

tryAcquire

java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire

    /**
     * 尝试以独占模式获取锁
     */
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

acquire

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire

    /**
     * 以独占模式获取,忽略中断
     * tryAcquire:如果tryAcquire获取锁,则直接跳出判断条件,不再执行后续操作
     *             如果tryAcquire没有获取锁,就继续执行acquireQueued(node,arg)方法,进行排队等待锁
     */
    @ReservedStackAccess
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&	
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

    /**
     * Convenience method to interrupt current thread.
     * 如果acquireQueued为True,就会执行selfInterrupt方法
     */
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter

    /**
     * 将当前线程封装成一个Node加入等待队列,返回值为当前Node
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;	//获取尾节点,如果不为空,将其作为当前节点的前置节点,并通过CAS操作将当前节点置为尾节点
        if (pred != null) {
            node.prev = pred;		
            if (compareAndSetTail(pred, node)) {
                pred.next = node;	//将前置节点的next指针指向已经成为尾节点的当前节点
                return node;
            }
        }
        enq(node);	//当前尾节点为空或者第一次尝试CAS操作失败,则进入完整的入队方法
        return node;
    }

    /**
     * 将节点插入队列,必要时进行初始化
     */
    private Node enq(final Node node) {
        for (;;) {	//自旋通过CAS将当前节点插入,直到入队成功为止
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))	//初始化
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {	//作为尾节点插入
                    t.next = node;
                    return t;
                }
            }
        }
    }

将当前Node加入等待队列之后,就需要尝试让队列动起来,因为这是一个先入先出的队列。需要让队伍不断的向前推进,让队伍后面的节点更快获取锁。

java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued

    /**
     * 把放入队列中的线程不断去获取,直到获取成功或者不再需要获取(中断)
     */
    @ReservedStackAccess
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;	//是否成功拿到资源
        try {
            boolean interrupted = false;	//标记等待过程中是否中断过
            for (;;) {	//自旋,要么获取锁成功,要么中断
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {	//如果当前节点前置节点为头节点并且当前线程尝试获取锁成功
                    //获取锁成功,当前节点移动到头节点
                  	setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
              	//p为头节点并且没有获取到锁或者p不是头节点,判断当前node是否需要阻塞,防止无限循环浪费资源
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
              	//将Node的waitStatus置为CANCEL,以及其他的一些清理工作
                cancelAcquire(node);
        }
    }

    /**
     * 判断是否需要挂起当前线程(通过前置节点的waitStatus)
     * 如果返回true,则代表当前节点需要被挂起
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * 如果前置节点状态为SIGNAL,说明前置节点也在等待拿锁
             * 说明当前节点可以挂起休息,直接返回true
             */
            return true;
        if (ws > 0) {
            /*
             * 如果前置节点状态大于0,那么状态只可能是CANCEL,所以可以将节点从队列中删除
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);	//删除前面所有waitStatus>0的Node
            pred.next = node;
        } else {
            /*
             * 通过CAS将前置节点的waitStatus置换为SIGNAL
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

    /**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);		//将当前线程挂起
        return Thread.interrupted();	//返回当前线程的中断状态并清除
    }

关于线程中断相关:https://www.zhihu.com/question/41048032

tryRelease

java.util.concurrent.locks.AbstractQueuedSynchronizer#tryRelease

    /**
     * 以独占模式释放锁
     */
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

release

java.util.concurrent.locks.AbstractQueuedSynchronizer#release

    /**
     * 独占模式释放锁
     * tryRelease:如果tryAcquire返回true,说明锁没有被任何线程持有
     */
    @ReservedStackAccess
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)	//如果头节点不为空并且waitStatus不等于0
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

    /**
     * 唤醒后续节点(如果存在)
     *
     * @param node the node
     */
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);	//将头节点的waitStatus置为0默认值,这样才不会影响其他函数的判断

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {	
            s = null;
            //从尾节点开始搜索,找到除head节点之外一个最靠前并且waitStatus小于0的节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
          	//对其进行取消中断操作,唤醒该节点起来干活
            LockSupport.unpark(s.thread);
    }

为什么唤醒节点的操作不同头开始往后搜索,而是要从后往前搜索?

参考美团文章:https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html#:~:text=为什么要从后往前找第一个非Cancelled的节点呢?原因如下。

参考文章

https://www.bilibili.com/video/BV12K411G7Fg/?spm_id_from=333.788&vd_source=1cb3897146f4b6f7475f43d795c1f9ba
https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html
https://javadoop.com/post/AbstractQueuedSynchronizer

posted @ 2023-02-23 14:56  不颓废青年  阅读(31)  评论(0编辑  收藏  举报