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的实现原理还是比较简单的。

猜你喜欢

转载自blog.csdn.net/heroqiang/article/details/79822250