AbstractQueuedSynchronizer源码解析

前言

本文主要从源码角度解析AQS(AbstractQueuedSynchronizer)的各个方法(之后文章中统一简称AQS)。

1. AQS的基本结构

AQS是一个构建锁和其他同步组件(如:ReentrantLock,CountDownLatch,Semaphore…)的基础框架。它的父类是AbstractOwnableSyncrhonizer,里面包含了2个方法:setExclusiveOwnerThread(Thread thread)和getExcelusiveOwnerThread()。set方法用来设置资源独占线程,get方法用来获取最近一次被set方法设置的线程。资源独占线程的意思是当一个线程获取到资源后,给这个资源上锁,其他线程在获取该资源的线程释放掉锁之前,无法再获取该资源。

1.1 AQS的大致流程

加锁过程概述:

首先会调用AQS中的acquire方法里的tryAquire方法进行加锁(tryAcquire方法实现的方法有公平锁和非公平锁2种)。

  • 如果获取成功,state加1并且通过父类方法AbstractOwnableSynchronizer的set方法设置资源独占线程。
  • 如果获取失败,说明前面已经有线程占用了该资源,所以当前线程需要进行排队等待资源释放。所以AQS将当前线程封装成为一个Node类对象放入双向链表中,之后通过Locksupport.park()阻塞当前线程。当他被排在他前面的那个线程唤醒之后,他将继续调用tryAcquire方法进行加锁操作,知道获得该资源,并把自身node节点设为头节点,并把之前的头节点去掉。

解锁过程概述:

通过调用AQS中的release方法里的tryRelease方法解锁的同时唤醒下一个有效的后继节点。

AQS加锁解锁流程图

1.2 AQS维护了一个FIFO的双向链表以及一个state属性变量

AQS的核心思想是当一个线程尝试获取资源时,如果资源处于闲置的状态,则将该线程设置为工作线程,并且将该资源设置为锁定状态。在资源处于锁定状态时,如果有其他线程来尝试获取资源,则会通过一个队列的形式将这些线程进行阻塞排队。实现这个排队队列的数据结构是基于CLH(Craig, Landin, and Hagersten)单向队列锁的变种来实现的一个双向,FIFO的双向链表锁(CLH相关实现不是本文的重点,大家有兴趣可以自行查阅相关资料和文档)。

下面我们来看看AQS维护的这个链表的结构与实现:
如果线程获取资源: 调用acquire(1)方法,acquire会调用tryAcquire(1)的抽象方法尝试获取资源。tryAcquire实现大致分为2中:公平锁获取,非公平锁获取(具体区别会在后面的文章中提到)。

  • 如果获取成功:state属性加1,并通过父类中的setExclusiveOwnerThread方法设置当前线程为该资源的资源独占线程。
  • 如果获取失败:说明之前已经有线程抢占了该资源,该线程则会进行排队。AQS会把该线程封装成Node对象放入存放Node的双向队列中,然后通过park()方法阻塞当前线程。之后该节点会一直处于等待睡眠状态,直到它上之前的节点(通常为它上一个节点)通过unpark方法把它唤醒。这时,它会循环调用tryAcquire()方法尝试获取资源,直到获取到资源将自身节点设置为head节点。如果获取失败则继续排队。

如果线程释放资源:调用release方法,release方法调用tryRelease()方法尝试释放资源,如果释放成功state减1,如果此时state为0,通过父类的setExclusiveOwnerThread(null)方法将资源独占线程设为0,再通过unpark()方法唤醒下一个节点的线程。值得注意的是tryRelease()方法只在Sync类中实现,所以解锁的过程并不区分公平锁与非公平锁。

AQS双向链表结构图
AQS双向链表出队
AQS双向链表出队

1.3 AQS维护了一个ConditionObject单向链表内部类

ConditionObject提供了条件锁的同步条件,实现了Condition接口。主要作用是为了并发编程中的同步提供了等待通知的实现。当线程不满足某些条件是,可以让线程挂起等待,直到满足条件时被AQS双向链表中的某个节点唤醒并进入AQS双向队列中。
Condition单向链表

2.AQS源码

2.1 成员变量

    /**
     * 设置用来支持CAS。这里我们需要通过native方法来实现:为了允许之后能够进行增强,
     * 我们不能明确的继承AtomicInteger,虽然这回更高效和有用。所以,我们本地实现hotspot
     * 内部的API。
     */
    //以下属性都用于cas操作
    //unsafe工具类对象
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //state变量的内存偏移量
    private static final long stateOffset;
    //head节点变量的内存偏移量
    private static final long headOffset;
    //tail节点变量的内存偏移量
    private static final long tailOffset;
    //Node节点的waitStatus属性的内存偏移量
    private static final long waitStatusOffset;
    //Node节点的next属性的内存偏移量
    private static final long nextOffset;

	//静态代码块初始化上述属性
    static {
        try {
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head")); 
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));  
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }

    /**
     * CAS head field. Used only by enq.
     */
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

    /**
     * CAS tail field. Used only by enq.
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }

    /**
     * CAS waitStatus field of a node.
     */
    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }

    /**
     * CAS next field of a node.
     */
    private static final boolean compareAndSetNext(Node node,
                                                   Node expect,
                                                   Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }

2.2 Node内部类

    static final class Node {
        /** 表示一个在共享模式中处于等待状态的标识 */
        static final Node SHARED = new Node();
        /** 表示一个在独享模式下处于等待状态的标识 */
        static final Node EXCLUSIVE = null;
        /** waitStatus的值,表示该线程已经被取消 */
        static final int CANCELLED =  1;
        /** waitStatus的值,表示后继线程需要被唤醒 */
        static final int SIGNAL    = -1;
        /** waitStatus的值,表示线程处于条件等待状态 */
        static final int CONDITION = -2;
        /** waitStatus的值,表示下一次执行acquireShared方法需要无条件的传播(
        只有当线程处于SHARED状态时,才会使用该字段) */
        static final int PROPAGATE = -3;

        /**
         * Status属性, 只能为以下这些值:
         *   SIGNAL:     这个节点的后继节点被(或者马上将被park方法)阻塞,所以当前节点
         * 				 必须在他释放锁或者被取消时唤醒他的后继节点。为了避免竞争,aquries方法
         *               必须首先声明他们需要一个signal,然后重试原子的aquire方法,如果失败,则
         * 				 阻塞。
         *   CANCELLED:  这个节点因为超时或者中断而被取消。节点进入该状态后不会在变化。
         * 				 特别是,当一个线程处于被取消的节点后不会再被阻塞。
         *   CONDITION:  这个节点正处于一个条件队列里。除非它的状态被置为0并
         * 				 被转移到同步队列里,否则它不会被当成一个同步队列节点
         *   PROPAGATE:  一个releaseSHared需要被传播给其他节点。
         * 				 这个值在doReleaseShared中被设置(仅仅对于头节点)来确保继续传播,
         *   0:          初始状态
         * 
         */
          
        /**
         * 表示【当前节点在队列中的状态】
         * 
         * 这个属性在正常的sync节点里被初始化为0,在condtion节点里被设置为CONDITION。
         * 这个属性是通过CAS进行修改的(或者在可能的情况下,无条件的volatile写入)。
         */
        volatile int waitStatus;

        /**
         * 【前驱指针】
         * 
         * 指向当前节点/线程所依赖的先驱节点来检查waitStatus。
         * 在入队的过程中进行设置,在出队之前被置为空(为了GC)。
         * 并且,在先驱节点被取消前,我们会在找到一个未被取消的节点时执行短路操作,
         * 因为头结点一直存在不会被取消,所以这个操作将一直存在。
         * 一个节点只在成功的acquire后成为头节点。
         * 一个取消的线程永远不会成功获取,并且一个线程只能取消它自己。
         */
        volatile Node prev;

        /**
         * 【后继指针】
         * 
         * 指向当前节点/线程的后继节点,当前线程即将释放时将其唤醒。
         * 在入队时设置,当绕过取消的前驱节点时进行调整,在出队是置为空(为了GC)。
         * 因为直到附加操作前入队操作没有赋值给下一个前驱节点的属性,
         * 所以看到一个空的next节点的属性不一定代表这个节点在这个队列的末尾。、
         * 但是,如果一个next节点的值为空,我们可以通过从尾部向前扫描来double check一下。
         * 一个被取消的next节点被设置为指向自己而不是置为空,
         * 这样使得isOnSyncQueue方法判断起来更加容易
         */
        volatile Node next;

        /**
         * 【表示处于该节点的线程】
         * 
         * 当前节点中入队的线程。在构造函数中初始化,在使用后被置为空
         */
        volatile Thread thread;

        /**
         * 表示【下一个处于CONDITION状态的节点】
         * 
         * 指向下一个在Condition队列里的等待中的节点,或者特殊值SHARED。
         * 因为Condition队列只有在被独占模式持有时才能访问,
         * 所以在他们在Condition队列中等待时,
         * 我们只需要一个简单的链表队列来储存他们。
         * 他们随后被转移到一个队列中来重新获取。
         * 而且因为conditions只能是独占,我们用特殊值来表示共享模式并储存在属性里。
         */
        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         * 如果节点在共享模式中处于等待状态返回true
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * Returns previous node, or throws NullPointerException if null.
         * Use when predecessor cannot be null.  The null check could
         * be elided, but is present to help the VM.
         * 返回上一个节点,如果为空则抛出空指针异常。
         * 当predecessor不能为空时使用。
         * @return the predecessor of this node
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

2.3 获取锁相关方法

	 /**
     *
  	 * 独占模式获取,忽略打断。通过至少调用一次tryAcquire()方法来实现。如果成功,
  	 * 直接返回。否则线程将排队,在调用tryAcquire方法成功前可能反复的阻塞与解阻塞。
  	 * 这个方法可以被用来实现Lock类中的lock方法。
  	 * 
     */
	 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    	}
    	
    /**
     *
     * 在当前线程和指定模式下创建一个节点并且入队
     * 
     */
    private Node addWaiter(Node mode) {
        //将当前线程和指定模式封装成一个Node对象
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //入队分为以下两种情况:
        //1.队尾不为空,
        //则说明Node链表已经被初始化,
        //直接通过compareAndSetTail方法将新的node
        //加在队尾即可
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //2.队尾为空,
        //则说明Node链表还没有被初始化,
        //通过enq方法初始化Node链表并将自己加在队尾
        enq(node);
        return node;
    }

    /**
     * 
     * 将一个节点入队,如果没有tail则初始化
     * 
     */
    private Node enq(final Node node) {
        //自旋cas,防止其他线程已经初始化了这个链表
        for (;;) {
            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;
                }
            }
        }
    }

    /**
     * 
     * 以不间断独占模式获取已经在队列里的线程。
     * 用于Condtion wait方法和acquire方法
     * 
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	//获取当前节点的上一个节点
                final Node p = node.predecessor();
                //这里分为2种情况:
                // 1. 当前节点的上一个节点为头节点,进行下一步尝试获取同步
                // 2. 当前节点的上一个节点不是头节点,排队
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    //能进入该方法说明tryAcquire方法获得了同步,并加锁成功,
                    //说明当前节点的上一个节点已经完成任务并释放掉锁了,
                    //直接将其置为空帮助GC
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //进行到这里,说明上一个条件没有成立
                //分2种情况:
                // 1. 当前节点的上一个节点不是头节点
                // 2. 当前节点的上一个节点是头节点但是tryAcquire尝试获取同步失败
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    /**
     * 
     * 检查并更新没能成功获取同步的节点的状态。如果线程应该被阻塞返回true。
     * 这个是所有acquire循环的主要信号控制。
     * 
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * 
             * 如果ws==Node.SIGNAL,说明前驱节点已经设置状态要求通知下一个节点,
             * 所以他的后继节点可以安全的park。
             * 
             */
            return true;
        if (ws > 0) {
            /*
             * 
             * 如果ws>0,说明前驱节点的前驱节点取消了。
             * 设置前驱节点为原前驱节点的上一个节点,
             * 知道新的前驱节点的状态不为CANCELLED。
             * 
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 
             * 其他情况,waitStatus 一定等于0或者PROPAGATE。
             * 表明我们需要一个signal,但是暂时不要park。
             * 调用者需要再试一次,确保他不能获取同步之后再进行park。
             * 
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false; 
    }
    
    /**
     *
     * park并且检查线程是否被中断
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    /**
     * 取消节点的获取状态
     *
     */
    private void cancelAcquire(Node node) {
       //如果节点不存在,直接返回
        if (node == null)
            return;

		// 将该节点的线程置空
        node.thread = null;

        // 跳过被取消的前驱节点
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

		// 获取过滤后的前驱节点的后继节点
        Node predNext = pred.next;

		// 将该节点的状态置为CANCELLED
        node.waitStatus = Node.CANCELLED;

        // 如果节点为tail节点,直接之前获取的过滤后的前驱节点的后继节点置为尾节点
        //如果CAS成功,将tail的后继节点置为空
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
            //如果失败
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            int ws;
            // 如果当前节点的前驱节点不是head节点
            // 继续判断
            // 1.当前节点的前驱节点的waitStatus是否为SIGNAL
            // 2.尝试把当前节点的前驱节点置为SIGNAL状态看是否成功
            // 如果1,2有一项返回true,则把当前节点的前驱节点的线程置为空
            // 如果以上判断条件返回true,把当前节点的前驱节点的next指针指向当前节点的后继节点
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            }
            //如果当前节点的前驱节点是head节点,或者以上条件都不满足,唤醒当前节点的后继节点 
            else {
                unparkSuccessor(node);
            }
            node.next = node; // help GC
        }
    }


2.4 释放锁相关方法

    /**
     * 
     * 在独享模式中释放资源。如果tryRelease返回true通过unblocking1个或多个线程来实现
     * 这个方法可以被用来实现Lock类中的unlock方法。
     * 
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            //如果头节点不为空,并且头节点的等待状态不是默认状态
            //唤醒他的后继节点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

    /**
     * 
     * 唤醒指定节点的后继节点(如果存在的话)
     * 
     */
    private void unparkSuccessor(Node node) {
        /*
         * 
         * 如果waitStatus为负数(比如SIGNAL)尝试清除预期的信号。
         * 如果这个操作失败或者状态被等待中的线程改变也没有关系。
         * 
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * 线程将要唤醒的节点在他的后继节点中,通常是下一个节点。
         * 但是如果下一个节点被取消或者为空时,从tail往前找知道找到真实的
         * 没有被取消的后继节点
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值