由Semaphore源码切入,学习AQS

   AQS是concurrent包的基础和核心,但的确很难理解,因为用的模板方法模式,所以它要适配各种并发工具,看源码的时候里面就会产生很多让人百思不得其解的地方。再加上,独特的代码风格,阅读起来的确很费劲。今天尝试由Semaphore切入,来理解下AQS相关的设计。用Semaphore来切入的原因是Semaphore代码比较简单一些,主要就一个acquire方法和一个release方法,用来理解代码设计会轻松一些。

Semaphore介绍

  源码上的注释说的是,Semaphore 从概念上讲维护了一组permits(许可)。在能获得许可之前,所有尝试执行acquire方法的线程将被阻塞。每一次执行relase方法后,都将增加一个许可,并有可能唤醒被阻塞住的线程。Semaphore常被用来限制尝试去获取资源的并发访问的线程数。
  简单来说,就是给并发设定一定数量的许可,许可量归0的时候则阻塞后续的线程,直到有线程释放许可。
  示例可以参考https://blog.csdn.net/huyaowei789/article/details/106690603

源码分析

先看acquire方法

public void acquire() throws InterruptedException {
   	sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

重点是tryAcquireShared方法,这是一个AQS的钩子方法,是子类必须实现的方法。此方法需要查询当前同步状态并判断同步状态是否符合预期,然后再CAS设置同步状态。Semaphore这里有两种实现方式,一种是公平锁的方式,一种是非公平锁的方式:

//公平锁
protected int tryAcquireShared(int acquires) {
   for (;;) {
   		//hasQueuedPredecessors方法判断当前线程是否在同步队列队首,若不是直接返回-1,执行后续进入同步队列的逻辑。
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}
//非公平锁
 final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

这个方法比较简单,就是取出当前剩余的许可量,然后减去要尝试获取的许可量,如果计算值大于等于0则通过cas设置新的许可量剩余值,若计算值小于0。则执行后续方法doAcquireSharedInterruptibly,当前线程可能就会进入同步队列等待。
doAcquireSharedInterruptibly方法是AQS中定义的一个模板方法。作用就是在共享可中断模式下获取许可,失败则进入同步队列等待

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            //判断是否是队首节点
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

addWriter方法是通过cas的方式,将当前节点插入同步队列队尾,失败则执行eng方法不断重试。然后判断当前节点是否是队首节点,若是则再次尝试尝试获取许可,失败则重试,成功则执行setHeadAndPropagate方法,将当前节点设为头节点,以便让当前节点出队,再执行doReleaseShared方法尝试唤醒后续节点(这里注意当前节点的调用方就是当前线程,所以不用唤醒当前节点。这里容易搞糊涂了)。若不是,则会先执行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.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        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.
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

shouldParkAfterFailedAcquire会判断前继节点状态,若是Signal则会直接返回true,然后执行parkAndCheckInterrupt方法阻塞当前线程。若不是,且状态大于0(大于0则代表当前是取消状态)则会跳过前继节点之前所有状态是取消的节点。若前两者都不是,则会将前继节点状态设为Signal,然后由外层方法循环重试,以保证在阻塞前当前线程是无法获取到许可的(真麻烦,甚至感觉还有点啰嗦,但这也是严谨的体现)。
接下来是release方法:

 public void release() {
    sync.releaseShared(1);
}

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

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))
            return true;
    }
}

原理相同,tryReleaseShared同样是个钩子方法,由Semaphore实现,会将当前剩余许可量加上你需要释放的许可量。若计算值小于还小于当前值,则直接报错。否则通过CAS重试设置当前状态为最新值。
然后是模板方法doReleaseShared:

private void doReleaseShared() {
    /*
     * Ensure that a release propagates, even if there are other
     * in-progress acquires/releases.  This proceeds in the usual
     * way of trying to unparkSuccessor of head if it needs
     * signal. But if it does not, status is set to PROPAGATE to
     * ensure that upon release, propagation continues.
     * Additionally, we must loop in case a new node is added
     * while we are doing this. Also, unlike other uses of
     * unparkSuccessor, we need to know if CAS to reset status
     * fails, if so rechecking.
     */
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

这个方法的主要作用就是去唤醒同步队列中等待的节点(前面获取步骤中也调用过此方法哦)。首先若头节点的状态是Signal,则更新头节点状态并唤醒后续节点。若状态为0(初始状态)则尝试更新头节点状态为PROPAGATE?这里不太明白,注释说的是确保释放后继续传播,可能这个地方更其他同步工具的使用有关系,以后需要慢慢分析。最后如果头节点变动了,说明有并发竞争发生,则重新循环以上步骤。

  以上就是对Semaphore源码的分析,Semaphore应该算是并发包下很简单的同步工具了,也推荐大家把这个类当作入口来学习AQS。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值