AQS 实现分析

原创
2016/10/18 20:17
阅读数 151
AbstractQueuedSynchronizer

很多必发工具类 中是通过AQS实现的,比如说 ReentrantLock 内部 的公平锁和非公平锁。

 

如在LinkedBlockingQueue  阻塞队列中,使用了Reentrantlock 

ReentrantLock putLock = new ReentrantLock();  //内部初始化一个非公平的队列
Condition notFull = putLock.newCondition();

如put方法 

LinkedBlockingQueue  
public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    // Note: convention in all put/take/etc is to preset local var
    // holding count negative to indicate failure unless set.
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();   //取得锁的资源
    try {
        while (count.get() == capacity) {
            notFull.await();   //释放资源,等待条件唤醒
        }
        enqueue(node);  //加入到单向链表的tail
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();  //唤醒等待资源,还有资源
    } finally {
        putLock.unlock();   //释放锁资源
    }
    if (c == 0)
        signalNotEmpty();   //唤醒非空的条件线程,使得阻塞的take线程继续运行。
}

分析 如何获取锁的资源,接着 

ReentrantLock.  lockInterruptibly

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

而sync就为AQS的子类,上面方法在AQS中已实现,

public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))   //子类实现
        doAcquireInterruptibly(arg);
}

而对于非公平的锁,在tryAcquire中的实现中,调用了如下,直接尝试获取资源。而不同于公平的锁,要判断当前线程是否是内部维护的等待链表的头节点的next节点线程。要按照节点的顺序来获取资源。

ReentrantLock Sync
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

失败后,继续进入 如下 获取方式

AQS
private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);  //加入双向链表的tail,如果为空,则new一个Node的标识为节点,内部数据为null,代表占用资源的线程。
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { //判断前节点是否是占用资源的节点,而后尝试
                setHead(node);  //占用资源后,将当前节点置为head,head表示占用资源的节点。
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&   //前节点是否是singnal,不是则将前点cas为signal
                parkAndCheckInterrupt())    //阻塞,并检测是否中断
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

然而,即使前节点是head,但是head还没有释放资源时,判断pred的status是否是等待,如果不是且小于0,将pred有status置为singnal,等待信号,而后park, 等待unpark(release时 会将head的next unpark)。就是根据前节点状态决定是否阻塞。如果前继节点处于CANCELLED状态,则顺便删除这些节点重新构造队列。

 

锁的过程就介绍完了。

接下来解锁

ReentrantLock   unlock

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

也需将节点park,也就是将节点unpark

AQS的实现
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);  //唤醒后继节点
        return true;
    }
    return false;
}

其中当前线程释放资源,如果只lock过一次,释放成功。

ReentrantLock 
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

唤醒后继节点后,在次获取。非公平的锁,唤醒后不一定成功,有可能被新加入的线程获得,从而导致刚被唤醒的线程再次被阻塞。

总结:

AQS中的tryAcquire 与tryRelease是 核心,根据不同的逻辑需求去实现  资源 的获取与释放。

参考

http://blog.csdn.net/chen77716/article/details/6641477

http://www.blogjava.net/zhanglongsr/articles/356782.html

http://www.infoq.com/cn/articles/java-blocking-queue/

http://www.cnblogs.com/paddix/p/5405678.html

 

展开阅读全文
打赏
0
2 收藏
分享
加载中
更多评论
打赏
0 评论
2 收藏
0
分享
返回顶部
顶部