AQS源码阅读

2 篇文章 0 订阅

前言

AbstractQueuedSynchronizer(AQS)类其实就是一个普通的工具类,用来控制资源并发访问的工具类,它只关心资源可以被谁获取被谁拥有,以及如何处置无法获取资源的对象,至于是如何获取和释放,则由子类来实现。在java.util.concurrent包中有很多类都实现了AQS,常用的有ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore,其中ReentrantLock实现了AQS中的独占锁,CountDownLatch、Semaphore实现了共享锁,ReentrantReadWriteLock两种都实现了。接下来让我们看看AbstractQueuedSynchronizer内部是如何实现并发控制的吧。

相关定义

AQS相关属性

 /**
 * 等待队列的头结点
 */
 private transient volatile Node head;

/**
* 等待队列的尾结点
*/
private transient volatile Node tail;

/**
* 同步结点状态,该状态用来保证线程同步,相当于锁,线程都会来修改该状态,默认为0
*	如果大于0表示有线程得到了锁,该数为多少就表示同一个线程加锁了多少次,所以可以实现为可重入锁
*/
private volatile int state;

AQS内部类Node

static final class Node {
  
    // 共享锁
    static final Node SHARED = new Node();
    
  	// 独占锁
    static final Node EXCLUSIVE = null;
    
  	// 节点状态之一,表示取消竞争,将会从队列中移除
    static final int CANCELLED =  1;
    
  	// 节点状态之一,表示等待唤醒
    static final int SIGNAL    = -1;
    
  	// 节点状态之一,表示等待条件状态,配合Condition类使用
    static final int CONDITION = -2;
  
    // 节点状态之一,表示状态需要向后传播
    static final int PROPAGATE = -3;
  	
  	// 节点状态,初始值为0,其它值有上面四个
  	volatile int waitStatus;

    // 前一个节点
    volatile Node prev;

    // 后一个节点
    volatile Node next;

    // 节点中的线程
    volatile Thread thread;

    // 等待条件的下一个节点
  	Node nextWaiter;
}

独占锁

锁的获取

acquire()
public final void acquire(int arg) {
  	// tryAcquire方法尝试获取锁,由子类实现,所以既可以实现为公平的排队获取,也可怕实现为非公平的抢占式获取
  	// 如果获取失败(修改state状态),则创建一个节点加入到队列中去,acquireQueued会返回中断状态
  	// 如果线程中断状态为true,则调用selfInterrupt方法进行中断
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
addWaiter()

此方法用来将当前线程添加至队列末尾,添加完成后还未将线程挂起

private Node addWaiter(Node mode) {
    // 由于不是共享模式,传入进来的mode是null,通过改Node构造方法,会将mode赋值给node中的nextWaiter
    Node node = new Node(Thread.currentThread(), mode);
    // 通过CAS操作将该节点插入到队列末尾
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
          pred.next = node;
          return node;
        }
    }
	  // 如果没插入成功,说明有竞争情况,调用enq进行自旋插入
    enq(node);
		return node;
}
enq()
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
acquireQueued()

在将当前线程添加至队列末尾之后还未将当前线程挂起,还会调用此方法尝试获取锁,如果没获取到,将会把当前线程挂起

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
          	// 获取前一个节点
            final Node p = node.predecessor();
          	// 如果前一个节点是头结点,则尝试获取锁,如果获取成功,那么将当前节点设置为头结点
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
          	// shouldParkAfterFailedAcquire方法判断是否需要挂起当前线程,如果返回false,则当前线程没有被挂起,进行下一次循环尝试获取锁
          	// parkAndCheckInterrupt方法用来挂起线程并返回当前线程的中断状态
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
shouldParkAfterFailedAcquire()

此方法用来判断是否可以挂起当前线程,并清理队列中已经取消竞争的节点

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
      	// 如果前一个节点的waitStatus为SIGNAL时,返回true,表示当前线程可以挂起
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
      	// 如果大于0,表示waitStatus为CANCELLED,则遍历队列将所有状态大于0的节点删除
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
      	// 其它情况则将当前节点的前一个节点waitStatus设置为SIGNAL,此时当前方法会返回false
      	// 当前线程不会挂起,在循环中再一次尝试获取锁,如果没获取到,再次进入本方法就会进入第一个判断返回true,挂起当前线程
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
获取锁总结
  1. 调用tryAcquire尝试获取锁(由子类实现),成功则直接返回,失败则进行下一步
  2. 调用addWaiter方法将当前线程添加到队列末尾,并返回当前线程组成的节点
  3. 添加到队列末尾之后,调用acquireQueued再次尝试获取锁
    • 获取成功,则将当前节点设置为头节点
    • 获取失败,则将当前线程在此挂起,下次唤醒还会进入循环中尝试获取锁

释放锁

release()
public final boolean release(int arg) {
  	// 释放锁成功之后,调用unparkSuccessor()方法唤醒头结点的后继节点
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

unparkSuccessor()

private void unparkSuccessor(Node node) {
    // 如果状态为负,则清除状态
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    // 获取头节点的下一个节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
      	// 如果下一个节点为null或者状态为取消状态,则从尾结点开始向头节点遍历,直到最后一个(即离头节点最近的)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);
}

本方法唤醒头结点的后继节点之后,线程会在上面的acquireQueued()方法的循环体中醒来,然后在循环中尝试获取锁。

以上所讲的都是AQS中独占锁,下面开始看一下AQS中的共享锁是如何实现的。

共享锁

锁的获取

acquire方法
// 普通的获取共享锁方法
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

/**
 * Acquires in shared uninterruptible mode.
 * @param arg the acquire argument
 */
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
          	// 如果前一个节点是头节点,则再尝试获取锁
            if (p == head) {
                int r = tryAcquireShared(arg);
              	// 获取到的r表示还剩多少,如果大于0表示还可以继续竞争锁
                if (r >= 0) {
                  	// 将当前节点设置为头节点并唤醒后面的节点
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
          	// shouldParkAfterFailedAcquire方法可以查看上面的解析
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
setHeadAndPropagete
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
    /*
     * 如果当前节点的下一个节点是共享节点,则释放
     */
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

锁的释放

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

private void doReleaseShared() {
    /*
     * 这里的代码要结合上面的doAcquireShared方法和里面的shouldParkAfterFailedAcquire方法看
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
          	// 如果头节点的waitStatus == Node.SIGNAL,则表明还有后续节点,则更改头节点状态为0,唤醒后续节点
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
          	// 如果头节点waitStatus == 0,则表明没有后续节点,那么将头节点状态设置为PROPAGATE,这样下一个添加进来的后续节点在shouldParkAfterFailedAcquire方法中判断状态不为SIGNAL,会再次尝试获取锁
            else if (ws == 0 &&
                    !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值