首页 > java > AQS原理分析

AQS原理分析

作者:bin

AQS简介

AQS指的就是AbstractQueuedSynchronizer
中文直译就是:抽象队列同步器

这里我们直接摘AQS代码的注释:

* Provides a framework for implementing blocking locks and related
* synchronizers (semaphores, events, etc) that rely on
* first-in-first-out (FIFO) wait queues. This class is designed to
* be a useful basis for most kinds of synchronizers that rely on a
* single atomic {@code int} value to represent state. Subclasses
* must define the protected methods that change this state, and which
* define what that state means in terms of this object being acquired
* or released.

大概翻译一下:提供一个框架,基于FIFO(先进先出)队列的同步器,此类使用一个原子的int类型值state,并且他的子类必须定义protected方法去改变这个state,包括acquired或released

所以总结3点:
1.AQS是一个框架,ReentrantLock、Semaphore、CountDownLatch都是他的经典实现
2.他用到一个先进先出队列
3.用一个原子的int类型做状态state
4.继承他的类,要实现aquire和release方法

原理分析

我们以reentranlock的lock为入口

方法lock调用到:AbstractQueuedLongSynchronizer.acquire()

   public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

看代码,这里使用模版方法,AQS会调用子类的tryAcquire去获取锁,获取锁的逻辑由子类实现,如果子类获取锁失败,就会尝试入队操作。
AQS使用的队列是一个双向链表,节点的ADT就是内部类的Node,那么是如何做入队,出队操作的呢?AQS调用自己的acquireQueued()方法

acquireQueued()方法

final boolean acquireQueued(final Node node, int arg) {
    //addWaiter()新增的node节点,使用当前线程作为节点
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor(); //p前驱节点
            if (p == head && tryAcquire(arg)) { //前驱节点是head,再次尝试获取锁
                setHead(node); //获取锁成功,将自己设置为head节点
                p.next = null; // 删掉前驱节点的后继指向,便于GC回收
                failed = false;
                return interrupted; //返回入队失败
            }
            if (shouldParkAfterFailedAcquire(p, node) && //判断是否要挂起节点,如果需要挂起,将前驱节点waitstatu改为Node.SIGNAL(-1)
            parkAndCheckInterrupt()) //挂起线程
            interrupted = true; //返回入队成功
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
}
//这里注意循环第一遍的时候,前驱节点如果为head,那么会先获取尝试获取一次锁,
//获取失败的话,将前驱节点的waitstatus设置为SIGNAL,表示 前驱节点的后继节点(即当前node) 需要挂起等待
//
//在第二次循环的时候前驱节点如果为head,那么会再尝试获取一次锁,
//获取失败的话,并且因为waitstatus已设置为SIGNAL,那么会将当前线程挂起

reentranlock的unlock

方法unlock调用到:AbstractQueuedLongSynchronizer.release()

同理调用子类的tryRelease去释放锁,释放成功后调用unparkSuccessor唤醒队列的后继线程

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

我们看unparkSuccessor()

private void unparkSuccessor(Node node) {
    //node为head节点
    int ws = node.waitStatus;
    if (ws < 0) compareAndSetWaitStatus(node, ws, 0); //修改状态为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); //后继节点不为空,唤醒后继节点
}

您必须 [ 登录 ] 才能发表留言!