天天看点

AbstractQueuedSynchronizer(AQS)源码解析一

这章不会对Condition部分进行讲解

抽象的队列同步器 AQS

对锁资源获取的过程进行了抽象,具体的获取过程留给子类去实现

子类不用去关心在获取不到锁资源的情况下,当前线程如何被挂起,如何被唤醒

这些都是由AQS来实现,获取资源和释放资源是典型的模板方式模式应用

AQS的重要属性

AbstractQueuedSynchronizer(AQS)源码解析一

双向链表来控制锁资源的获取

头节点是获取到锁的线程,thread会被置为null

AbstractQueuedSynchronizer(AQS)源码解析一

节点信息

节点的两种模式:共享、独占

节点的五种状态:初始化状态、CANCELLED、SIGNAL、CONDITION、PROPAGATE

CONDITION、PROPAGATE只会在共享模式出现

AbstractQueuedSynchronizer(AQS)源码解析一

了解了上面的信息后,我们直接进入今天的重点。

AQS获取独占锁 不可中断的

/**
     *  获取独占锁   
     *  tryAcquire 留给需要的子类实现  模板方法模式
     * @param arg 需要的资源数量
     */
    public final void acquire(int arg) {
        //tryAcquire    尝试获取失败 
        //addWaiter 添加独占模式的节点到线程同步队列
        //acquireQueued  会返回当前线程是否需要被标记为中断的  true 将当前线程标记为中断的 
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //设置当前线程中断的标志位为true
            selfInterrupt();
    }

	 /**
     * 	尝试获取独占锁
     * 	具体策略由子类实现
     * @return true 获取成功 false 获取失败
     */
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
           

tryAcquire方法是由具体的锁实现对资源的获取,AQS中protected是让子类有选择的去实现,如果用Abstract子类就必须要去实现它了,不然自己也成了抽象类了。

当tryAcquire获取资源失败了,后续怎么处理呢?别慌,AQS都帮我们实现好了,让我们不用关心,这就是AQS出现的理由。

我们来看看AQS是怎么处理。

当自己实现的tryAcquire获取独占锁失败之后,addWaiter(Node.EXCLUSIVE),会保证将独占模式的节点加入到阻塞队列中。

AbstractQueuedSynchronizer(AQS)源码解析一

可以看到死循环(自旋)、CAS用到的很多

acquireQueued

1.如果前驱是头节点就再调用tryAcquire,尝试获取一下锁

2.node是不是需要被挂起且挂起当前线程并返回线程的中断状态

3.继续循环

AbstractQueuedSynchronizer(AQS)源码解析一

shouldParkAfterFailedAcquire(p, node),判断node获取锁失败后是不是被挂起。

AbstractQueuedSynchronizer(AQS)源码解析一

parkAndCheckInterrupt,挂起当前线程(等待被唤醒)并且会返回线程的中断状态

/**
     *	 挂起当前线程(等待被唤醒)
     *	 会返回线程的中断状态
     *	 @return true 当前线程被标记中断的 false 没有被标记中断的
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        //返回当前的线程中断位标志 如果是true的话 会将中断标志重置为false
        return Thread.interrupted();
    }
           

这里要注意一下,为什么方法会返回中断状态呢?,是因为有些获取锁的方法是可以被中断的。

获取共享锁

/**
     *	获取共享锁
     */
    public final void acquireShared(int arg) {
    	//由子类实现 小于0说明获取失败
        if (tryAcquireShared(arg) < 0)
        	//获取共享锁 AQS实现
            doAcquireShared(arg);
    }
    /**
     * 	尝试获取共享锁
     * 	具体策略由子类实现
     * @return 返回剩余的可用资源数
     */
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
           

都是一样的套路,留给子类实现。

doAcquireShared

1.*addWaiter前面已经说过了,只是这里添加的是共享模式的节点

2. 如果是前驱是头节点的话,就尝试获取共享锁,也是调用子类的tryAcquireShared,获取完之后,如果还有可用的资源,就向后传播,并唤醒其它等待锁的节点

3. node是不是需要被挂起且挂起当前线程并返回线程的中断状态

4. 继续循环

AbstractQueuedSynchronizer(AQS)源码解析一

setHeadAndPropagate,把node设置同步队列的头节点,如果还有可用的资源数,则继续唤醒后续节点

AbstractQueuedSynchronizer(AQS)源码解析一

shouldParkAfterFailedAcquire和parkAndCheckInterrupt**获取独占锁那里有可以看下。

独占锁和共享锁的获取已经说了现在再来说说释放

释放独占锁

/**
     * 	释放独占锁
     * @return true 释放成功 false 释放失败
     */
    public final boolean release(int arg) {
    	//尝试释放独占锁 留给需要的子类实现
        if (tryRelease(arg)) {
        	//独占锁释放成功 唤醒后继节点
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	//唤醒node的后继节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    /**
     * 	尝试释放独占锁
     * 	具体策略由子类实现
     * @return true 获取成功 false 获取失败
     */
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
           

tryRelease,由子类实现。

unparkSuccessor,唤醒node的后继节点。

AbstractQueuedSynchronizer(AQS)源码解析一

释放共享锁

/**
     * 	释放共享锁
     */
    public final boolean releaseShared(int arg) {
    	//尝试释放共享锁成功 子类实现
        if (tryReleaseShared(arg)) {
            //唤醒后继阻塞节点
        	doReleaseShared();
            return true;
        }
        return false;
    }
           

doReleaseShared

AbstractQueuedSynchronizer(AQS)源码解析一

总结

上面说的获取锁和释放锁都只是其中的一种方式,AQS还提供了,可中断、可超时的获取和释放锁的方式,关于AbstractQueuedSynchronizer完整的源码解析点击这里。后续我会用JUC里面提供的一系列并发工具类来更具体的说明AQS。

继续阅读