JUC 之 Semaphore 信号量

JUC 之 Semaphore 信号量

注意:学习本篇内容之前请先阅读【AQS 之 共享锁 源码剖析】一篇,AQS 是基础

在某些场景中我们需要控制接口或某些方法在某一时刻的访问次数,防止一下涌入大量请求压垮我们的服务,Java 信号量就能完成该功能。Semaphore 维护了一组许可,每次访问接口或方法前必须先获得许可(访问凭证),拿到凭证才有访问权限,不然将被阻塞。接下来我们就来看看具体实现。

进入源码之前我们先来看个使用示例:

static class SemaphoreDemo {
    private static final int MAX_AVAILABLE = 100;
    // 创建一个 Semaphore 对象,拥有 100 个访问许可(资源池)
    private final Semaphore semaphore = new Semaphore(MAX_AVAILABLE);
    public Object getItem() throws InterruptedException {
        semaphore.acquire();// 获取许可
        return getNextAvailableItem();// 拿到许可后执行业务程序
    }
    public void putItem(Object x) {
        if (markAsUnused(x)) semaphore.release();// 释放自己持有的资源(还回资源池)
    }
	// 以下代码属于业务代码
    protected Object[] items = new Object[MAX_AVAILABLE];
    protected boolean[] used = new boolean[MAX_AVAILABLE];
    protected synchronized Object getNextAvailableItem() {
        for (int i = 0; i < MAX_AVAILABLE; ++i) {
            if (!used[i]) {
                used[i] = true;
                return items[i];
            }
        }
        return null;
    }
    protected synchronized boolean markAsUnused(Object item) {
        for (int i = 0; i < MAX_AVAILABLE; ++i) {
            if (item == items[i]) {
                if (used[i]) {
                    used[i] = false;
                    return true;
                } else return false;
            }
        }
        return false;
    }
}

通过该示例代码我们发现 Semaphore 的核心就是 acquire 与 release 这两个方法。下面我们就进入 Semaphore 的源码之旅。

构造器

// 默认创建非公平锁机制
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
//根据 fair 选择创建 公平或非公平
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

Sync 内部类

通过下述源码我们发现在初始化 Sync 的时候提前将 AQS 的 state 变量初始化为我们传入的 permits 容量,线程在获取到锁就将 state 减 1,当 state 等于 0 时说明已经没有使用凭证了,再有线程请求时将交由 AQS 负责阻塞、唤醒。

abstract static class Sync extends AbstractQueuedSynchronizer {
    Sync(int permits) {
        setState(permits);// 初始化 可用资源数
    }
    final int getPermits() {
        return getState();// 获取当前可用资源数
    }
}
nonfairTryAcquireShared 方法

非公平的方式抢资源,将 state 减去 acquires(用户需要的资源数),当 state < 0 时,返回通知 AQS 获取锁失败,AQS 负责线程阻塞及唤醒操作。

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();// 保存当前 state 值,当前可用资源数
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))// CAS 更新 state 值,
            return remaining;
    }
}
tryReleaseShared 方法

释放资源操作,将持有的资源数加回 state 中

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();// 保存当前 state 值,当前可用资源数
        int next = current + releases; // 将 releases 加回 state 中
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        if (compareAndSetState(current, next))// CAS 更新 state 值
            return true;
    }
}

NonfairSync 内部类

非公平机制实现

static final class NonfairSync extends Sync {

    NonfairSync(int permits) {
        super(permits);// 调用 Sync 构造器初始化 state 
    }
	// 调用 Sync 的 nonfairTryAcquireShared 方法获取凭证
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

FairSync 内部类

公平机制实现

static final class FairSync extends Sync {
    FairSync(int permits) {
        super(permits);// 调用 Sync 构造器初始化 state 
    }

    protected int tryAcquireShared(int acquires) {
        for (;;) {
            // 不管资源池中是否有可用资源,优先判断 AQS 的 竞争队列中是否有排队节点,有,排队等待,否则去资源池中获取访问凭证
            if (hasQueuedPredecessors())
                return -1;
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

核心方法

acquire 方法

获取访问凭证,间接调用 AQS 提供的可中断获取共享锁的方法(acquireSharedInterruptibly) ,此方法在【AQS 之 共享锁 源码剖析】已经讲解过,此处不在赘述

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

释放凭证,归还资源池,调用 AQS 的 releaseShared 方法完成释放操作,此方法在【AQS 之 共享锁 源码剖析】已经讲解过,此处不在赘述

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

总结:利用 AQS 的共享锁机制,初始设置一个固定 state 值(初始容量),当有线程需要获取访问权时,检测 state 是否有可用资源,state > 0 代表有资源可用,获取到访问凭证后将 state 减 1;只要 state > 0 其他线程就可以获取访问凭证,直到 state = 0。利用该特性就可以完成对接口的限流操作,控制并发请求量。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值