队列同步器(AQS)理论与源码解析

前言

AQS是AbstractQueuedSynchronizer的简称,提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,如图所示:在并发包中AQS有着非常重要的地位,本文就来深扒AQS的实现机制。

队列同步器(AQS)理论与源码解析_第1张图片

正文

同步器使用

在这里以ReentrantLock为例,同步器的主要使用方式是继承并实现抽象方法管理同步状态,可以看下源码:
队列同步器(AQS)理论与源码解析_第2张图片

同步器提供了三个用来管理同步状态的方法,分别是
 

protected final int getState() {
        return state;
    }

   
    protected final void setState(int newState) {
        state = newState;
    }

 
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

AQS的子类通常定义为同步组件的静态内部类,如同ReentrantLock中所示,因为同步器本身没有实现任何接口,仅仅定义了同步状态的操作方法供组件使用。此外,同步器支持独占氏和共享氏两种获取资源的状态,基于此就可以实现自定义的组件(例如ReentrantLock、ReentrantReadWriteLock、CountDownLatch),这里给大家解释一下独占氏与共享氏,独占氏指的是某个资源特定时间只能有一个线程获取锁,其他获取这个锁的线程只能处理同步队列中等待,只有当前线程释放了锁,后续线程采用获取锁。共享氏:在同一个时刻可以有多个线程获取同步状态,比如读写锁

同步器可重写方法

同步器的设计是基于模版方法模式,使用者需要重写指定的方法,然后将同步器组合在自己的组件中使用,同步器提供了以下可重写的方法,在调用的过程中会使用重写的方法

  • boolean tryAcquire(int arg): 独占式的获取同步状态,实现该方法需要先获取并判断同步状态是否符合预期,再通过CAS设置同步状态。(即使用compareAndSetState(expect, update)方法)。可用于实现Lock接口中的tryLock()方法。=
  • boolean tryRelease(int arg): 独占式的释放同步状态,等待获取同步状态的线程将有机会获取同步状态,实现Lock的unlock()。
  • int tryAcquireShared(int arg): 共享式的获取同步状态,返回大于等于0的值表示获取成功,否则获取失败。
  • boolean tryReleaseShared(int arg): 共享式的释放同步状态
  • boolean isHeldExclusively(): 用于获取同步器是否在独占模式下被线程占用,一般用来表示是否被当前线程所独占。

同步器的模版方法

实现自定义的同步组件,需要调用同步器中模板方法,而模板方法内部则调用的是同步器的可重写方法

  • void acquire(int arg): 独占式的获取同步状态,会调用tryAcquire()重写方法。如果当前线程获取同步状态成功,则直接返回;否则,进入同步队列等待。
  • void acquireInterruptibly(int arg): 与acquire()相同,但可以响应中断。如果当前线程获取同步状态失败,进入同步队列,直到获取成功或响应中断。如果响应中断,会抛出中断异常(InterruptException)。
  • boolean tryAcquireNanos(int arg, long nanosTimeout): 在acquireInterruptibly()的基础上增加了等待时间,如果等待超时,则返回false;如果在等待的时间内,成功获取则返回true。
  • boolean release(int arg): 独占式的释放同步状态,并在释放同步状态之后,唤醒同步队列中第一个节点包含的线程。
  • void acquireShared(int arg): 共享式的获取同步锁,与独占式获取的主要区别:同一时刻可以有多个线程获取到同步状态。
  • void acquireSharedInterruptibly(int arg): 与acquireShared()相同,但可以响应中断。
  • boolean tryAcquireSharedNanos(int arg, long nanosTimeout): 在acquireSharedInterruptibly()的基础上增加了等待时间。
  • boolean releaseShared(int arg): 共享式的释放同步状态
  • Collection getQueuedThreads(): 获取在同步队列中等待的线程集合。

自定义组件的例子

这个例子是实现的一个独占氏的锁,对于重写方法、模版方法、同步器的组合都非常明白的定义了出来

public class Mutex implements Lock {

    /**
     * 队列同步器的extend
     */
    private static class Sync extends AbstractQueuedSynchronizer{

        /**
         * 是否处于占用状态
         * @return
         */
        @Override
        protected boolean isHeldExclusively(){
            return getState()==1;
    }

        /**
         * 状态为0时获取锁
         * @param acquires
         * @return
         */
        @Override
        public boolean tryAcquire(int acquires){
            if (compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        @Override
        protected boolean tryRelease(int release){
            if (getState()==0){
                throw new IllegalMonitorStateException();
            }

            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        Condition newCondition(){
            return new ConditionObject();
        }
    }

    private final Sync sync = new Sync();

    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireSharedNanos(1,unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

队列同步器的实现

前面在介绍资源的获取方式的时候提到过同步队列,没有获取到锁的线程将会同步队列中等待。那么这个同步队列是如何实现的呢?

在同步器的内部维护了一个FIFO的同步队列,同步器内部定义了Node节点,节点包含了线程需要获取资源的各种信息,同步队列就是由这些节点链接而成的,并且指明了头/尾节点。

同步队列如图所示(来自网络):千万别说这是个链表(因为我想过,哈哈)这可是满足FIFO原则的队列同步器(AQS)理论与源码解析_第3张图片

最后我们看一下线程获取独占氏获取锁的流程
队列同步器(AQS)理论与源码解析_第4张图片

 节点加入队列尾部的源码如下:
队列同步器(AQS)理论与源码解析_第5张图片

尝试循环添加节点

队列同步器(AQS)理论与源码解析_第6张图片

最后节点添加成功后,就进入自旋状态,不断的判断条件,满足条件就获取同步状态

队列同步器(AQS)理论与源码解析_第7张图片
队列同步器(AQS)理论与源码解析_第8张图片

你可能感兴趣的:(Java并发编程)