AbstractQueuedSynchronizer源码分析(二):独占锁的获取与释放

1、典型实现:ReentrantLock

ReentrantLock就是一个典型的不响应中断的独占锁,那就从ReentrantLock的lcok()开始走读不响应中断的独占锁的实现逻辑。
这里我们基于ReentrantLock默认的非公平锁的lock(),公平锁我们留待分析ReentrantLock的源码中进行详细分析。
先上NonfairSync代码

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * 立即尝试获取锁,失败后再去走正常的锁获取流程。
         * (非公平不需要根据队列顺序来获取锁,直接尝试获取锁可以很高的提升锁的效率)
         */
        final void lock() {
        	//0表示还未有线程获得锁
            if (compareAndSetState(0, 1))
            	//设置独占锁的拥有者
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

NonfairSync的类关系是NonfairSync extends Sync,Sync extends AbstractQueuedSynchronizer。这里采用的模板模式进行设计的,AbstractQueuedSynchronizer的acquire(int arg)方法已经完成了不响应中断的独占锁获取逻辑,而tryAcquire(int acquires)是对state锁标识位的管理,需要子类实现。

2、AbstactQueuedSynchronizer不响应中断的独占锁方法介绍

AbstactQueuedSynchronizer中供子类实现的方法

方法名 作用
boolean tryAcquire(int arg) 尝试以独占模式获取。此方法应该查询对象的state是否允许以独占模式获取它,如果允许,那么可以获取它。
boolean tryRelease(int arg) 尝试以独占模式设置state来反映对锁的释放。
boolean isHeldExclusively() 同步器是否在独占模式下被当前线程占用。

AbstactQueuedSynchronizer中实现的方法

方法名 作用
void acquire(int arg) 在独占模式下获取锁,不响应中断
Node addWaiter(Node mode) 以mode模式为当前线程创建node节点,并且加入CLH队列中。
boolean compareAndSetTail(Node expect, Node update) CAS更新tail属性
Node enq(final Node node) 将node节点加入队尾,若队列未初始化,先进行初始化,在同步器整个生命周期中只会初始化一次
boolean compareAndSetHead(Node update) CAS更新head属性
boolean acquireQueued(final Node node, int arg) 以独占不响应中断模式为已在队列中的线程获取锁
void setHead(Node node) 把node设为CLH队列的head节点,并将node节点thread释放、把原head节点从CLH队列中释放
boolean shouldParkAfterFailedAcquire(Node pred, Node node) 寻找安全点,当找到安全点后进行阻塞
void LockSupport.park(Object blocker); 当前线程阻塞
boolean compareAndSetWaitStatus(Node node,int expect,int update) CAS更新waitStatus属性
boolean parkAndCheckInterrupt() 阻塞当前线程,并在唤醒后返回中断状态
void cancelAcquire(Node node) 取消线程获取锁
boolean compareAndSetNext(Node node,Node expect,Node update) CAS更新next属性
void unparkSuccessor(Node node) 唤醒后继节点中的线程
boolean compareAndSetState(int expect, int update) CAS更新state属性
void selfInterrupt() 标记当前线程中断状态
  1. CAS方法介绍
    在上面的方法中,我们发现#compareAndSetTail,#compareAndSetHead,#compareAndSetWaitStatus,#compareAndSetNext,#compareAndSetState方法都是CAS方法。拉出源码一看,方法体内部是通过调用unsafe#compareAndSwapObject方法属性的更新。
    关于CAS的介绍,可以参考【Java中的CAS应用
    下面源码中,展示了AQS那些属性可以进行CAS操作。
	private static final Unsafe unsafe = Unsafe.getUnsafe();
	// AbstractQueuedSynchronizer.class的属性
	// state属性下标,以下雷同
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;
    // Node.class的属性
    // waitStatus属性下标,以下雷同
    private static final long waitStatusOffset;
    private static final long nextOffset;

    static {
        try {
        	// 获取属性state在AbstractQueuedSynchronizer类中的下标,以下雷同
            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); }
    }
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }
    private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
    }
    private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }
  1. acquire(int arg)方法源码及注释
	// 在独占模式下获取锁,忽略中断;参数arg传输给#tryAcquire方法,可以表示任何你想要表达的意思。
	public final void acquire(int arg) {
        // 1、tryAcquire(arg),调用同步器实现的tryAcquire获取锁,返回true流程结束,否则进入2
		// 2、addWaiter(Node.EXCLUSIVE),将当前线程封装成node节点,加入到CLH队列尾部,进入3
		// 3、若线程从queued中被唤醒时,返回中断状态,若为true,进入4
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
			// 4、将中断状态传输给当前线程
            selfInterrupt();
    }
    /** 
	* 这里的tryAcquire需要AQS的子类(同步器)进行实现,提供给父类的模板方法调用。
	* return 返回true表示获取锁成功,否则表示获取锁失败
	*/
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
  1. addWaiter(Node mode) 方法源码及注释
	/**
     * 以mode模式为当前线程创建node节点,并且加入CLH队列中。
     *
     * @param mode Node.EXCLUSIVE 独占模式, Node.SHARED 共享模式
     * @return 创建的node节点
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // node节点尝试快速入队,若失败进入enq()方法
        Node pred = tail;
        if (pred != null) {
			// 设置node节点的前驱,无多线程并发,放在CAS外
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
				// 存在多个线程并发操作pred节点,必须在节点入队成功后,把node设为pred节点的后继,。
				// 必须要CAS成功后才能操作。
                pred.next = node;
                return node;
            }
        }
		// node节点入队
        enq(node);
        return node;
    }
  1. enq(final Node node)方法源码及注释
	/**
     * 将node节点加入队尾,若队列未初始化,先进行初始化,在同步器的整个生命周期中只会初始化一次
     * @param node 加入队尾的node节点
     * @return node节点的前驱节点
     */
    private Node enq(final Node node) {
		// 通过循环,不断尝试将node加入队尾;直至成功,跳出循环。
        for (;;) {
            Node t = tail;
            if (t == null) { 
				// 初始化队列,Node节点中不包含线程
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
					// 加入队尾成功,跳出循环。
                    t.next = node;
                    return t;
                }
            }
        }
    }
  1. acquireQueued(final Node node, int arg)源码及注释
	/**
     * 以独占不响应中断模式获取已在队列中的线程,直至tryAcquire(arg)方法返回true(获取锁成功),并返回中断状态。
     *
     * @param node 当前线程的node节点
     * @param arg acquire方法参数
     * @return 如果等待时被中断,返回true,否则返回false
     */
    final boolean acquireQueued(final Node node, int arg) {
		// 失败状态
        boolean failed = true;
        try {
			// 中断状态
            boolean interrupted = false;
			// 进行循环,直至tryAcquire成功后,返回中断状态。
            for (;;) {
				// 获取前驱节点
                final Node p = node.predecessor();
				// 1、前驱节点为head && tryAcquire获取锁返回true,返回中断状态,否则进入2
                if (p == head && tryAcquire(arg)) {
					// 将该节点设为head节点,并将node节点中的线程释放
					setHead(node); 
					// 释放前驱节点的引用,帮助GC
                    p.next = null; 
					// 标记失败状态
                    failed = false; 
                    return interrupted;
                }
				// 2、shouldParkAfterFailedAcquire,判断node节点是否应该阻塞,返回true进入3,否则进入1
				// 3、parkAndCheckInterrupt,node节点进入阻塞状态,被唤醒后返回中断状态,返回true,标记interrupted为true,否则不标记;随后进入1
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
			// 判断失败状态,若为true;取消node节点acquire。根据上面流程看,个人认为只有在出现异常时才会出现cancelAcquire(node)的情况。
            if (failed)
                cancelAcquire(node);
        }
    }
  1. setHead(Node node)源码及注释
	/**
     *把node设为CLH队列的head节点,并从CLH队列中出列。
	 *
     */
    private void setHead(Node node) {
		// 设为head节点
        head = node;
		// 释放node节点中线程
        node.thread = null;
		// 释放对前驱节点的引用,帮助GC
        node.prev = null;
    }
  1. shouldParkAfterFailedAcquire(Node pred, Node node)源码及注释
	/**
     * 获取锁失败后,根据前驱判断当前节点是否应该进行阻塞。
     *
     * @param pred 前驱节点
     * @param node 当前节点
     * @return 返回true,当前线程应该阻塞
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		// 获取pred节点waitStatus状态值
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
			 /*
             * 若pred节点为SIGNAL状态,表示pred节点释放锁时会唤醒(unpark)后继节点;同时也表示node节点可以安全的进行阻塞(park)了。
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
			 /*
             * waitStatus>0表示前驱节点被取消,放弃当前的pred节点,不断循环寻找前驱节点,直至寻找到一个未被取消的节点。
             */
            do {
				// 1、pred = pred.prev,获取pred的前驱节点
				// 2、node.prev = pred.prev,当前节点指向新的前驱节点
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
			// 寻找成功后,前驱节点next引用node节点
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
			 /*
             * 上面两种状态都不满足时,通过CAS操作更新pred节点为waitStatus=SIGNAL
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
		// 不能park,需要不断遍历,直到前驱节点waitStatus=SIGNAL
        return false;
    }
  1. LockSupport.park(Object blocker)源码
    具体关于LockSupport的内容,可以参考【LockSupport的使用】,不做赘述。
	//这行代码的作用就是使线程阻塞在这里,等待其他的线程调用该unpark该线程,唤醒线程后继续执行后面方法
	LockSupport.park(this);
  1. parkAndCheckInterrupt()源码及注释
	/**
	* 现在阻塞在当前,当前驱线程调用unpark方法后,可以唤醒阻塞,并返回中断状态。
	*/
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
  1. cancelAcquire(Node node)源码及注释
	/**
     * Cancels an ongoing attempt to acquire.
     *
     * @param node the node
     */
	 /**
     * 取消node节点,
	 * 若node节点的prev是正常节点,连接node.prev和node.next
	 * 若node节点的prev是非正常状态的节点,就唤醒node节点的后继节点
     */
    private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
		// node不存在,忽略后面逻辑
        if (node == null)
            return;
		// 释放node节点中线程
        node.thread = null;

        // Skip cancelled predecessors
		// 跳过所有被取消的前驱
        Node pred = node.prev;
        while (pred.waitStatus > 0)
			// 1.pred = pred.prev 将pred的前驱节点设为pred
			// 2.node.prev = pred 将pred设置为node的前置节点
			// 这里的逻辑等于是放弃原来的pred节点,将pred.prev设为新的pred
            node.prev = pred = pred.prev;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
        // 取出pred前驱原值
		Node predNext = pred.next;
		
        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
		// 设置node节点状态为CANCELLED
        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
		// 如果node为tail节点,就将pred设为tail节点
        if (node == tail && compareAndSetTail(node, pred)) {
			// 释放pred节点的next属性
            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.
			// 若node后继节点需要唤醒,尝试将pred的next属性设为后继节点。否则就唤醒node节点
            int ws;
			// 1.pred节点不为head节点,ture进入2,否则进入6
			// 2.pred节点waitStatus为SIGNAL,true进入4,否则进入3
			// 3.当pred未被取消,并将其waitStatus更新为SIGNAL成功后进入4,否则进入6
			// 4.若pred的线程不为空进入5,否则进入6
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                // 5.若node.next节点是正常节点,将node.next节点地址指向pred.next地址,等于放弃了node节点了。
				Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
				// 6若pred节点不满足上面的情况,就需要直接唤醒node的后继节点,即node.next
                unparkSuccessor(node);
            }
			// 帮助GC
            node.next = node; // help GC
        }
    }
  1. unparkSuccessor(Node node)源码及注释
	/**
	 * 唤醒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);

        /*
         * 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;
			// 若node.next已经被取消,那需要重tail变量找到离node最近的那个节点,作为node节点后继节点唤醒
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
		// 唤醒node的后继节点
        if (s != null)
            LockSupport.unpark(s.thread);
    }
  1. selfInterrupt()源码及注释
	/**
     * Convenience method to interrupt current thread.
     */
	/**
     * 将中断状态传输给当前线程
     */
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

上面代码走读添加了很多注释,能够清晰的理解到不响应中断锁的获取方式。以上的注释和理解的
篇幅限制,相关的知识点就贴上相关链接了,也是我在走读代码时遇到的不懂,然后去了解的。若读者朋友们对其不是很熟悉,也可以先阅读相关文章,主要是【CAS】和【LockSupport】。

不响应中断的获取独占锁的流程图

附上不响应中断的获取独占锁的流程图
不响应中断的获取独占锁

3、AbstactQueuedSynchronizer响应中断的独占锁方法

在介绍响应中断的独占锁之前,需要了解什么是【中断】,在了解中断后。我们继续看acquireInterruptibly(int arg) throws InterruptedException方法,对比acquire(int arg)发现,acquireInterruptibly方法在获取到线程中断标记后,立即抛出InterruptedException异常以做响应。

方法名 作用
void acquireInterruptibly(int arg) throws InterruptedException 尝试回去哦响应中断的独占锁
doAcquireInterruptibly(int arg) throws InterruptedException 将线程加入CLH队列中进行管理

与不响应中断锁不同主要是以上两个方法,其他相同方法不在此赘述。

  1. void acquireInterruptibly(int arg) throws InterruptedException 若线程被标记中断,直接抛出中断异常
	public final void acquireInterruptibly(int arg)
            throws InterruptedException {
		// 线程中断立即响应
        if (Thread.interrupted())
            throw new InterruptedException();
		// 线程获取独占锁失败后,进行入队操作
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
  1. void doAcquireInterruptibly(int arg) throws InterruptedException 若线程被标记中断,直接抛出中断异常
	private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
		// 入队
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
			// 直至获取到锁或线程被标记中断
            for (;;) {
				// head节点(dummy)的next可以尝试获取锁
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    // node成为新的head节点
					setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
				// 1、寻找安全点,成功后进入2,否则再次进入循环
				// 2、进入阻塞,阻塞被唤醒后返回中断状态,若被标记中断,进入3,否则再次进入循环
				// 3、抛出中断异常
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
			// 抛出中断异常后,取消node节点
            if (failed)
                cancelAcquire(node);
        }
    }

4、AbstactQueuedSynchronizer独占锁的释放

方法名 作用
boolean release(int arg) 释放独占模式下的锁
void unparkSuccessor(Node node) 唤醒后继节点中的线程
  1. release(int arg)源码及注释
public final boolean release(int arg) {
		// 调用子类实现tryRelease方法,返回ture表示release成功,否则结束流程
        if (tryRelease(arg)) {
			// 释放锁成功,唤醒队列中第一个node节点中的线程,因为head节点是dummy节点,所以唤醒head.next节点中线程。
            Node h = head;
			// 此时的head节点的waitStatus应该为-1,是在调用acquire时设置的。
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

5、总结

从源码的走读和源码注解理解,了解到AbstactQueuedSynchronizer独占锁如何管理线程,如何阻塞,阻塞在何处,何时被唤醒,响应中断和不响应中断如何管理中断状态。定义了从尝试获取–阻塞–唤醒–尝试获取的整个流程。另外也知道锁的状态(state)管理完全是依赖子类对tryAcquire(int arg)和tryRelease(int arg)的实现。换句话说锁状态(state)是交由子类实现管理,AQS并不关心锁的管理,它只关心Thead何时如何怎样去获取锁。
下一章节介绍共享锁,请各位读者持续关注。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章