Semaphore 源码解析

阅读须知

  • JDK版本:1.8
  • 文章中使用/* */注释的方法会做深入分析

正文

Semaphore(信号量), 从概念上讲,信号量维护一套许可。每次 acquire 方法调用都会根据需要进行阻塞,直到获得许可为止,然后将其占用。每次 release 方法调用都会添加一个许可,可能会唤醒因没有获取到许可而阻塞的线程。Semaphore 基于 AQS 实现,不熟悉 AQS 的同学可以查阅笔者关于 AQS 源码分析的文章进行学习。关于 Semaphore 的使用我们就不过多赘述了,直接进入源码分析,我们首先来看一下 Semaphore 的构造方法:
Semaphore:

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

permits 为许可的数量,fair 变量确定使用公平锁或非公平锁。Semaphore 的另一个重载的只有 permits 参数的构造方法使用的是非公平锁。下面我们来看许可的获取操作:
Semaphore:

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

这里的 acquireSharedInterruptibly 方法我们在 AQS(AbstractQueuedSynchronizer)源码解析(共享锁)这篇文章中已经详细分析过 acquireSharedInterruptibly 方法,是可以响应中断的共享锁获取方法,方法中会调用由子类实现的 tryAcquireShared 方法实现共享锁获取逻辑,我们首先来看 Semaphore 非公平锁对于 tryAcquireShared 方法的实现:
Semaphore.NonfairSync:

protected int tryAcquireShared(int acquires) {
    /* 非公平共享锁获取 */
    return nonfairTryAcquireShared(acquires);
}

Semaphore.Sync:

final int nonfairTryAcquireShared(int acquires) {
    // 自旋
    for (;;) {
        // Semaphore 用 AQS 的 state 变量的值代表可用许可数
        int available = getState();
        // 可用许可数减去本次需要获取的许可数即为剩余许可数
        int remaining = available - acquires;
        // 如果剩余许可数小于0或者 CAS 将当前可用许可数设置为剩余许可数成功,则返回成功许可数
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

这里因为存在多线程竞争的情况,所以整个操作在自旋下完成。我们分析过 tryAcquireShared 方法的返回值,这里当剩余许可数大于等于0时,代表获取许可成功,小于0时代表获取许可失败,要进行阻塞等待。接下来我们来看 Semaphore 公平锁对于 tryAcquireShared 方法的实现:
Semaphore.FairSync:

protected int tryAcquireShared(int acquires) {
    for (;;) {
        // 这里多了一步判断,是否存在应该先于当前线程获得锁的线程
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

我们发现,公平锁与非公平锁对 tryAcquireShared 方法的实现的唯一区别就是公平锁首先会判断是否存在应该先于当前线程获得锁的线程,如果存在说明当前线程不是下一个应该获取锁的线程。hasQueuedPredecessors 方法主要确认以下几种情况:

  • 等待队列为空
  • 当前线程所在的节点是头结点
  • 当前线程所在的节点是头结点的后继节点

满足上述三个条件的任意一个,说明当前线程是下一个可以获取锁的线程,反之则说明当前线程不是下一个应该获取锁的线程,这时 tryAcquireShared 方法会返回-1代表获取共享锁失败。

下面我们来看释放许可操作,释放许可操作没有公平和非公平之分:
Semaphore:

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

这里的 releaseShared 释放共享锁方法同样来自于 AQS,方法中首先会调用由子类覆盖的 tryReleaseShared 方法,通过尝试设置state变量来释放共享锁,我们来看 Semaphore 对于 tryReleaseShared 方法的实现:
Semaphore.Sync:

protected final boolean tryReleaseShared(int releases) {
    // 自旋
    for (;;) {
        // 获取 AQS 的 state 值也就是当前剩余许可的数量
        int current = getState();
        // 将本次释放的许可数量累加到当前剩余许可的数量
        int next = current + releases;
        if (next < current)
            // 进入这里说明已经超过了 int 的最大值
            throw new Error("Maximum permit count exceeded");
        // CAS 设置本次释放操作后剩余的许可数量
        if (compareAndSetState(current, next))
            return true;
    }
}

整体来看,Semaphore 就是基于 AQS 的共享锁实现,如果理解了 AQS 的共享锁,理解 Semaphore 的实现原理还是比较简单的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值