java semaphore 源码_JDK源码分析-Semaphore

本文首发于微信公众号【WriteOnRead】,欢迎关注。

1. 概述

Semaphore 是并发包中的一个工具类,可理解为信号量。通常可以作为限流器使用,即限制访问某个资源的线程个数,比如用于限制连接池的连接数。

打个通俗的比方,可以把 Semaphore 理解为一辆公交车:车上的座位数(初始的“许可” permits 数量)是固定的,行驶期间如果有人上车(获取许可),座位数(许可数量)就会减少,当人满的时候不能再继续上车了(获取许可失败);而有人下车(释放许可)后就空出了一些座位,其他人就可以继续上车了。

下面具体分析其代码实现。

2. 代码分析

Semaphore 的方法如下:

fe76f683af1308499ebe74846f7f4f8c.png

其中主要方法是 acquire() 和 release() 相关的一系列方法,它们的作用类似。我们先从构造器开始分析。构造器private final Sync sync;

// 初始化 Semaphore,传入指定的许可数量,非公平

public Semaphore(int permits) {

sync = new NonfairSync(permits);

}

// 初始化 Semaphore,传入指定的许可数量,指定是否公平

public Semaphore(int permits, boolean fair) {

sync = fair ? new FairSync(permits) : new NonfairSync(permits);

}

构造器初始化了 Sync 变量,根据传入的 fair 值指定为 FairSync 或 NonFairSync,下面分析这三个类。内部嵌套类 Syncabstract static class Sync extends AbstractQueuedSynchronizer {

private static final long serialVersionUID = 1192457210091910933L;

// 构造器,将父类 AQS 的 state 变量初始化为给定的 permits

Sync(int permits) {

setState(permits);

}

// 非公平方式尝试获取许可(减少 state 的值)

final int nonfairTryAcquireShared(int acquires) {

// 自旋操作

for (;;) {

// 获取许可值(state),并尝试 CAS 修改为减去后的结果

int available = getState();

int remaining = available - acquires;

if (remaining < 0 ||

compareAndSetState(available, remaining))

return remaining;

}

}

// 释放许可(增加 state 的值)

protected final boolean tryReleaseShared(int releases) {

for (;;) {

// 操作与获取类似,不同的在于此处是增加 state 值

int current = getState();

int next = current + releases;

if (next < current) // overflow

throw new Error("Maximum permit count exceeded");

if (compareAndSetState(current, next))

return true;

}

}

// 一些方法未给出...

}

可以看到 Sync 类继承自 AQS,并重写了 AQS 的 tryReleaseShared 方法,其中获取和释放许可分别对应的是对 AQS 中 state 值的减法和加法操作。具体可参考前文对 AQS 共享模式的分析「JDK源码分析-AbstractQueuedSynchronizer(3)」。NonFairSync (非公平版本实现)static final class NonfairSync extends Sync {

private static final long serialVersionUID = -2694183684443567898L;

// 调用父类 Sync 的构造器来实现

NonfairSync(int permits) {

super(permits);

}

// 重写 AQS 的 tryAcquireShared 方法,代码实现在父类 Sync 中

protected int tryAcquireShared(int acquires) {

return nonfairTryAcquireShared(acquires);

}

}FairSync (公平版本实现)static final class FairSync extends Sync {

private static final long serialVersionUID = 2014338818796000944L;

// 构造器调用父类 Sync 的构造器来实现

FairSync(int permits) {

super(permits);

}

// 重写 AQS 的 tryAcquireShared 方法,尝试获取许可(permit)

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;

}

}

}PS: 体现“公平”的地方在于 tryAcquireShared 方法中,公平的版本会先判断队列中是否有其它线程在等待(hasQueuedPredecessors 方法)。

主要方法的代码实现:// 获取一个许可(可中断)

public void acquire() throws InterruptedException {

sync.acquireSharedInterruptibly(1);

}

// 获取一个许可(不响应中断)

public void acquireUninterruptibly() {

sync.acquireShared(1);

}

// 尝试获取一个许可

public boolean tryAcquire() {

return sync.nonfairTryAcquireShared(1) >= 0;

}

// 尝试获取一个许可(有超时等待)

public boolean tryAcquire(long timeout, TimeUnit unit)

throws InterruptedException {

return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));

}

// 释放一个许可

public void release() {

sync.releaseShared(1);

}

还有一系列类似的操作,只不过获取/释放许可的数量可以指定:// 获取指定数量的许可(可中断)

public void acquire(int permits) throws InterruptedException {

if (permits < 0) throw new IllegalArgumentException();

sync.acquireSharedInterruptibly(permits);

}

// 获取指定数量的许可(不可中断)

public void acquireUninterruptibly(int permits) {

if (permits < 0) throw new IllegalArgumentException();

sync.acquireShared(permits);

}

// 尝试获取指定数量的许可

public boolean tryAcquire(int permits) {

if (permits < 0) throw new IllegalArgumentException();

return sync.nonfairTryAcquireShared(permits) >= 0;

}

// 尝试获取指定数量的许可(有超时等待)

public boolean tryAcquire(int permits, long timeout, TimeUnit unit)

throws InterruptedException {

if (permits < 0) throw new IllegalArgumentException();

return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));

}

// 释放指定数量的许可

public void release(int permits) {

if (permits < 0) throw new IllegalArgumentException();

sync.releaseShared(permits);

}

可以看到,Semaphore 的主要方法都是在嵌套类 FairSync 和 NonFairSync 及其父类 Sync 中实现的,内部嵌套类也是 AQS 的典型用法。

3. 场景举例

为了便于理解 Semaphore 的用法,下面简单举例分析(仅供参考):public class SemaphoreTest {

public static void main(String[] args) {

// 初始化 Semaphore

// 这里的许可数为 2,即同时最多有 2 个线程可以获取到

Semaphore semaphore = new Semaphore(2);

for (int i = 0; i < 50; i++) {

new Thread(() -> {

try {

// 获取许可

semaphore.acquire();

System.out.println(Thread.currentThread().getName() + " 正在执行..");

// 模拟操作

TimeUnit.SECONDS.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

} finally {

// 释放许可

semaphore.release();

}

}).start();

}

}

}

/* 执行结果(仅供参考):

Thread-0 正在执行..

Thread-1 正在执行..

Thread-2 正在执行..

Thread-3 正在执行..

...

*/

这里把 Semaphore 的初始许可值设为 2,表示最多有两个线程可同时获取到许可(运行程序可发现线程是两两一起执行的)。设置为其他值也是类似的。

比较特殊的是,如果把 Semaphore 的初始许可值设为 1,可以当做“互斥锁”来使用。

4. 小结

Semaphore 是并发包中的一个工具类,其内部是基于 AQS 共享模式实现的。通常可以作为限流器使用,比如限定连接池等的大小。

相关阅读:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值