JUC框架 Semaphore源码解析 JDK8

前言

Semaphore在多线程协作中常常用来控制公共资源的访问,限制同时访问数量。

打个比方,Semaphore就像是一个装有令牌(permit)的黑箱子,拿到令牌的人才能去做爱做的事情,谁都可以从里面拿走若干令牌,谁都可以把新的令牌扔到里面去,但Semaphore从来不记载谁拿走的令牌。

重要成员

CountDownLatch一样,Semaphore依赖的也是AQS的共享锁,核心属性也是AQS的state成员。在Semaphore中,使用内部类继承AQS使用。

构造器

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }

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

默认构造器使用非公平锁,可以通过参数fair来得到公平锁。参数permits最终会赋值给AQS的state成员。

获取信号量(减小state)

Semaphore方法调用的AQS方法是否阻塞是否响应中断是否超时机制返回值及含义
acquire()sync.acquireSharedInterruptibly(1)-void
acquire(int permits)sync.acquireSharedInterruptibly(permits)-void
acquireUninterruptibly()sync.acquireShared(1)--void
acquireUninterruptibly(int permits)sync.acquireShared(permits)--void
tryAcquire(long timeout, TimeUnit unit)sync.tryAcquireSharedNanos(1, unit.toNanos(timeout))boolean
返回时是否获得了锁
tryAcquire(int permits, long timeout, TimeUnit unit)sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout))boolean
返回时是否获得了锁
tryAcquire()sync.nonfairTryAcquireShared(1) >= 0-boolean
返回时是否获得了锁
  • 上面表格中,所有没有参数的Semaphore方法,实质上都是在获得单位1的信号量。
  • 调用的AQS方法,其实只有三种,acquireShared共享锁的获取与释放中已经进行了讲解),acquireSharedInterruptiblytryAcquireSharedNanosCountDownLatch源码解析中已经进行了讲解)。这三者的区别已经在表格列出。
    • 需要响应中断,方法声明会抛出中断异常。
    • 有超时机制,就需要用返回值区别 获得锁返回 和 超时都没获得到锁 两种情况。
    • 这三个方法都需要调用到AQS子类实现的tryAcquireShared,该方法用来获取共享锁,子类可以将其实现公平锁或是非公平锁。
  • nonfairTryAcquireShared其实不大应该放在上表里面,因为它根本不是AQS的方法,只是AQS子类的新加方法。因为它根本没有阻塞等待的过程,只是简单的try一次,成功失败听天由命,所以它根本不会阻塞。

表格中,带permits参数的方法可以获得单位不为1的信号量,但是方法中对permits参数的检查要求是>=0,也就是说,允许线程获得0单位的信号量,虽然我感觉这样没有任何意义。

这里我们再复习一下tryAcquireShared返回值的含义:

  • 如果返回值大于0,说明获取共享锁成功,并且后续获取也可能获取成功。
  • 如果返回值等于0,说明获取共享锁成功,但后续获取可能不会成功。
  • 如果返回值小于0,说明获取共享锁失败。

非公平实现的tryAcquireShared

//NonfairSync
protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}

//Sync
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 || 
            compareAndSetState(available, remaining))
            return remaining;
    }
}
  • 方法使用了自旋,这是合理且必要的。共享锁是共享的,自然可能有多个线程正在同时执行上面的代码,即使失败了也不能退出循环,而是应该失败后再得到当前值,然后再次CAS尝试。
  • 如果remaining算出来小于0,说明剩余信号量已经不够拿的了,那就直接返回remaining这个负数(表达获取共享锁失败),不做CAS操作。
  • 如果remaining算出来大于等于0,说明剩余信号量够拿的,紧接着如果CAS设置成功,就返回remaining这个大于等于0的数(表达获取共享锁成功)。
  • 这个方法想要退出,只有当前线程拿到了想要数量的信号量,或剩余信号量已经不够拿。

公平实现的tryAcquireShared

//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;
    }
}

区别就是要先判断同步队列中是否已经有节点了(hasQueuedPredecessors),如果有那同步队列中的节点属于是排在当前线程之前的,所以只好直接返回-1。

释放信号量(增加state)

释放信号量就不用区别什么公平不公平了。

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;
    }
}
  • 考虑多线程,使用自旋保证releases单位的信号量能够释放到位。
  • 只有CAS设置成功,或溢出int型的范围,才能退出这个循环。

工具方法

tryAcquire

上面表格里,一直没有好好讲tryAcquire,因为它在里面属于一个异类,没有阻塞等待的过程。

    public boolean tryAcquire() {
        return sync.nonfairTryAcquireShared(1) >= 0;
    }

nonfairTryAcquireShared的实现也看过了,里面根本没有调用过LockSupport.park,确实没有阻塞。不要被自旋所迷惑,自旋也不是阻塞,而且这个自旋过程一般情形下很快就会结束。

简而言之,Semaphore#tryAcquire的作用就是尝试 一次性的、非公平的 获得锁动作。注意这种一次性动作一定要是非公平实现的,不然大部分情况下(同步队列中只要有一个线程在等待),这种一次性动作肯定不能成功。这也是为什么要把非公平实现放到NonfairSyncFairSync的父类里的一个公共方法里。

注意,返回值进行了处理,如果获得共享锁成功(nonfairTryAcquireShared返回值>=0),返回true;如果获得共享锁失败(nonfairTryAcquireShared返回值<0),返回false。

reducePermits

//Semaphore
protected void reducePermits(int reduction) {
    if (reduction < 0) throw new IllegalArgumentException();
    sync.reducePermits(reduction);
}

//Sync
final void reducePermits(int reductions) {
    for (;;) {
        int current = getState();
        int next = current - reductions;
        if (next > current) // underflow
            throw new Error("Permit count underflow");
        if (compareAndSetState(current, next))
            return;
    }
}
  • 这个方法的作用也是获得信号量,只不过这个函数相比nonfairTryAcquireShared的实现,它允许改变后的信号量是负数。
  • 自旋的过程。想要退出函数,只有CAS操作成功或者向下溢出了。
  • if (next > current)分支进入的原因只能是int型变量向下溢出,因为reductions被保证是一个>=0的数。
  • protected方法,用起来不是那么方便。
//Sync
final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 || 
            compareAndSetState(available, remaining))
            return remaining;
    }
}

放一下nonfairTryAcquireShared的实现作对比。

drainPermits

//Semaphore
public int drainPermits() {
    return sync.drainPermits();
}

//Sync
final int drainPermits() {
    for (;;) {
        int current = getState();
        if (current == 0 || compareAndSetState(current, 0))
            return current;
    }
}

自旋的过程,直到信号量被清空为0。

总结

  • Semaphore的AQS子类是一个很标准的共享锁实现。
    • 获得信号量 == 减小AQS的state。
    • 释放信号量 == 增加AQS的state。
  • 共享锁的AQS子类实现方法需要自旋,这一点在Semaphore和CountDownLatch都有体现。独占锁的AQS子类实现方法不需要自旋。
  • 获得信号量失败一定是因为信号量已经减到0了,且获得失败就会阻塞。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值