Semaphore源码解析

Semaphore信号量

Semaphore也是基于AQS实现的,AQS源码解析过程:AQS源码解析

构造方法

参数 permits 指的就是state值,Semaphore 默认使用的是非公平锁,但是也可以传fair参数使用公平锁

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

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

成员方法

获取锁

除了 acquire() 获取锁之外,还可以传入信号量获取 acquire(int permits),其实现和前者大同小异;

还有就是通过非阻塞 tryAcquire() 方法获取锁,即尝试获取锁,若未获取成功则直接返回结果,当然也有传入信号量 / 时间参数的非阻塞方法 tryAcquire(int permits) / tryAcquire(int permits, long timeout, TimeUnit unit) / tryAcquire(long timeout, TimeUnit unit),实现的逻辑也都差不多

以下主要以 acquire() 为例子

// 阻塞式获取锁
public void acquire() throws InterruptedException {
    // 调用AQS的acquireSharedInterruptibly方法
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
    // 如果遇到线程中断则抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
public boolean tryAcquire() {
    // 默认传的信号量为1
    return sync.nonfairTryAcquireShared(1) >= 0;
}
final int nonfairTryAcquireShared(int acquires) {
    // 自旋,直到获取锁
    for (;;) {
        // 获取定义的信号量值
        int available = getState();
        // 获取剩余的信号量值
        int remaining = available - acquires;
        // 若剩余信号量值小于0,说明不允许其他线程抢占资源,都得进入等待队列中排队
        // 若剩余信号量值大于0,并且state值cas成功,则获取资源成功
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

释放锁

释放锁通常是调用release方法;也和获取锁的方法一样,有着信号量和时间的重载方法

public void release() {
    // 也是调用AQS中的releaseShared方法,默认信号量也为1
    sync.releaseShared(1);
}
// AQS中的方法
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        // 唤醒线程
        doReleaseShared();
        return true;
    }
    return false;
}
// Semaphore重写AQS的方法tryReleaseShared
protected final boolean tryReleaseShared(int releases) {
    // 自旋,直至释放资源成功
    for (;;) {
        // 获取当前的总信号量
        int current = getState();
        // 释放资源,总信号量+1
        int next = current + releases;
        if (next < current)
            throw new Error("Maximum permit count exceeded");
        // 自旋修改总信号量
        if (compareAndSetState(current, next))
            return true;
    }
}

总结

其实 Semaphore 的底层实现很简单明了,和 countdownlatch 差不多都是围绕一个 int值 去判断能否 获取/释放 资源

案例

假设每一个线程都代表一辆车,每次有一辆车停车,就会显示剩余车位减一,每次有一辆车出去,显示屏上就显示剩余车位加1,当显⽰屏上的剩余车位为0时,停车场⼊⼝的栏杆就不会再打开,车辆就⽆法进⼊停车场了,直到有⼀辆车从停车场出去为⽌

public class Test {
    // 初始化
    static Semaphore semaphore = new Semaphore(5);
    static AtomicInteger sortNum = new AtomicInteger(0);
    private static ExecutorService executor = new ThreadPoolExecutor(10, 10, 6000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());

    private static void parkCar() {
        int i = sortNum.addAndGet(1);
        try {
            // 1号汽车进来尝试获取资源,发现信号量为5,减去自身1,大于0成功获取资源
            // 2号汽车也进来尝试获取资源,发现信号量为4,减去自身1,大于0成功也获取资源
            // 3号汽车也进来尝试获取资源,发现信号量为3,减去自身1,大于0成功也获取资源
            // ....
            // 直到6号汽车进来获取资源的时候,发现信号量为0,减去自身1,小于0,这时获取锁不成功,需要插入等待队列进行排队
            semaphore.acquire();
            System.out.println(i + "号汽车驶入停车场");
            // 1号汽车停车成功,花费3s,再这段时间里其实已经有很多辆车停进停车场了
            Thread.sleep(3000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 停车成功,开始释放资源,这时可能是释放2号/3号/5号...的资源
            // 总之释放的时候会将总信号量+1,并且唤醒等待队列中的汽车,在非公平的情况下可能是6号/8号/12号...
            // 这时唤醒的车辆又可以重新尝试获取资源进入停车场了
            semaphore.release();
            System.out.println(i + "号汽车驶出停车场");
        }
    }
    
    public static void main(String[] args) throws Exception {
        // 模拟多线程访问
        for (;;) {
            executor.execute(() -> parkCar());
            // 假设每辆车进入之后需要花费100ms
            Thread.sleep(100);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

99Savage

你的鼓励是我最大的动力!加油

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值