AbstractQueuedSynchronizer(AQS)解析

一行一行源码分析清楚AbstractQueuedSynchronizer

http://www.cnblogs.com/waterystone/p/4920797.html

AQS的经典使用案例推荐JDK中JUC组件源码:ReentrantLock、Semaphore、CountDownLatch等

 

内部数据结构

图中展示AQS类较为重要的数据结构,包括int类型变量state用于记录锁的状态,继承自AbstractOwnableSynchronizer类的Thread类型变量exclusiveOwnerThread用于指向当前排他的获取锁的线程,AbstractQueuedSynchronizer.Node类型的变量headtail。 
其中Node对象表示当前等待锁的节点,Nodethread变量指向等待的线程,waitStatus表示当前等待节点状态,mode为节点类型。多个节点之间使用prevnext组成双向链表,参考CLH锁队列的方式进行锁的获取,但其中与CLH队列的重要区别在于CLH队列中后续节点需要自旋轮询前节点状态以确定前置节点是否已经释放锁,期间不释放CPU资源,而AQSNode节点指向的线程在获取锁失败后调用LockSupport.park函数使其进入挂起阻塞状态,让出CPU资源。故在前置节点释放锁时需要调用unparkSuccessor()->LockSupport.unpark函数唤醒后继节点。 
根据以上说明可得知此上图图主要表现当前thread0线程获取了锁,thread1线程正在等待。

 

WaitStatus

4种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE。

  • CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。

  • SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。

  • CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。

  • PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。

  • 0状态:值为0,代表初始化状态。

 

源码分析

acquire获取排他资源、挂起当前线程

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
//由上面代码传入排他模式Node.EXCLUSIVE
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 尝试快速放入队尾; 失败则执行enq(node)      
        Node pred = tail;
        //当前存在队尾
        if (pred != null) {
            node.prev = pred;
            //CAS放入队尾
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //CAS入队失败,执行enq(node)入队
        enq(node);
        return node;
    }
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋
            for (;;) {
                
                final Node p = node.predecessor();
                //快速尝试
                //如果当前node的前节点是head,且tryAcquire成功                
                if (p == head && tryAcquire(arg)) {
                    //当前node设置为head,目的是减少线程挂起
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }

                //上边if不成功
                if (shouldParkAfterFailedAcquire(p, node) && //判断前节点状态waitStatus,决定是否继续自旋
                    parkAndCheckInterrupt())//不需要自旋则挂起线程park
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
}

唤醒下一个可用节点

private void unparkSuccessor(Node node) {

        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * 尝试唤醒下一节点。但是下节点waitStatus是cancelled or apparently时,
         * 从队尾tail向前,找到最前面的waitStatus <= 0节点,唤醒
         */
        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);
    }

 

 

简单的排它锁实现(非重入)

private class Sync extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            assert arg == 1;
            if (compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            assert arg == 1;
            if (!isHeldExclusively()) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively() {
            return getExclusiveOwnerThread()==Thread.currentThread();
        }
}

 

AQS主要作用就是控制线程的挂起等待与唤醒,是设计线程阻塞组件的重要框架。其通过队列结构保存线程节点。head为当前持有资源节点,其释放资源时AQS组件会唤醒队列中下一个节点的线程。线程挂起后只能等待其他线程将其唤醒。

aqs-0

 

在并发环境下,加锁和解锁需要以下三个部件的协调:

  1. 锁状态。我们要知道锁是不是被别的线程占有了,这个就是 state 的作用,它为 0 的时候代表没有线程占有锁,可以去争抢这个锁,用 CAS 将 state 设为 1,如果 CAS 成功,说明抢到了锁,这样其他线程就抢不到了,如果锁重入的话,state进行 +1 就可以,解锁就是减 1,直到 state 又变为 0,代表释放锁,所以 lock() 和 unlock() 必须要配对啊。然后唤醒等待队列中的第一个线程,让其来占有锁。
  2. 线程的阻塞和解除阻塞。AQS 中采用了 LockSupport.park(thread) 来挂起线程,head用 unpark 来唤醒下一节点线程。
  3. 阻塞队列。因为争抢锁的线程可能很多,但是只能有一个线程拿到锁,其他的线程都必须等待,这个时候就需要一个 queue 来管理这些线程,AQS 用的是一个 FIFO 的队列,就是一个链表,每个 node 都持有后继节点的引用。AQS 采用了 CLH 锁的变体来实现,感兴趣的读者可以参考这篇文章关于CLH的介绍,写得简单明了。

 

推荐阅读JDK中ReentrantLock、CountDownLatch源码理解AQS的使用。

他们都自己独立的 AbstractQueuedSynchronizer内部实现类Sync,控制线程的同步并发策略

 

AbstractQueuedSynchronizer中需要覆盖方法(钩子)

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。

  不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要CAS控制共享资源 volatile state 的获取与释放方式即可,至于具体线程等待队列的维护(如线程获取资源失败入队/被唤醒出队等),AQS已经在顶层通过LockSupport实现挂起和唤醒。开发者只用关注获取和释放的逻辑即可。自定义同步器实现时主要实现以下几个可以覆盖的钩子方法

  • isHeldExclusively():boolean:该线程是否正在独占资源。只有用到condition才需要去实现它。
  • tryAcquire(int):boolean:独占方式。尝试获取资源,成功则返回true。失败则返回false,加入阻塞队列挂起线程,等待被唤醒。
  • tryRelease(int):boolean:独占方式。尝试释放资源,成功则返回true,唤醒后续等待结点。失败则返回false。
  • tryAcquireShared(int):boolean:共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):boolean:共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

 

AbstractQueuedSynchronizer中提供的常用方法(非钩子)

acquire负责获取和挂起、release负责释放和唤醒

  • getState()
  • setState(int)
  • compareAndSetState():boolean
  • getExclusiveOwnerThread()
  • setExclusiveOwnerThread()
  • acquire(int):boolean    //LockSupport.park实现thread挂起。内部是for循环,线程从挂起时执行到LockSupport.park位置被唤醒后,执行循环再次尝试获得资源,若失败会继续运行到LockSupport.park挂起。
  • tryAcquireNanos(int arg, long nanosTimeout):boolean    //等待获取资源,超时返回。其通过tryAcquire()方法判断资源,底层用LockSupport.parkNanos(Object blocker, long nanos)实现挂起超时返回
  • acquireInterruptibly(int arg) //等待获取资源,中断立即抛出异常。
  • release(int):boolean  //释放+唤醒下一节点thread

acquireInterruptibly允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会立即抛出InterruptedException。

acquire方法不允许Thread.interrupt中断,即使检测到Thread.isInterrupted,一样会继续尝试获取锁,失败则继续休眠。只是在最后获取锁成功后,再中断线程。

posted @ 2018-09-19 23:53  sw008  阅读(156)  评论(0编辑  收藏  举报