许可证Semaphore

Semaphore是一个线程同步工具,用于限制同时访问特定资源的线程数量。它通过维护一系列许可来实现这一目标,线程在开始执行前需要获取许可,执行结束后释放。Semaphore提供了公平和非公平的获取许可方式,以及阻塞和非阻塞的获取策略。在Java多线程编程中,Semaphore常用于控制并发线程数,但不保证数据安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

Semaphore许可证

Semaphore原理分析

Semaphore方法总结

Semaphore总结


Semaphore许可证

Semaphore是一个线程同步工具,主要用于同一时刻允许一定数量的线程对共享资源并行操作的场景。

Semaphore可以用于限制用户人数类似的一些场景,比如停车场、高峰吃饭时的饭店位置等。

我们先通过一个例子了解它的用法:

public static void main(String[] args) {
    //限制10个
    final int permits = 10;
    //定义10个Semaphore许可证
    final Semaphore semaphore = new Semaphore(permits, true);
    //启动20个线程
    IntStream.range(0,20).forEach(i -> {
        new Thread(()->{
            boolean flag = semaphore.tryAcquire();
            if (flag){
                System.out.println(Thread.currentThread().getName()+"获取许可证");
            }else {
                System.out.println(Thread.currentThread().getName()+"没有获取许可证");
            }
            try {
                //模拟获取许可证之后的业务处理
                sleep();
            } finally {
                semaphore.release();
                System.out.println(Thread.currentThread().getName()+"释放获取许可证");
            }
        },"线程-"+i).start();
    });

}

private static void sleep() {
    try {
        TimeUnit.SECONDS.sleep(new Random().nextInt(3));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

上述代码中,勾勾创建了10个许可,也即是最多10个线程同时运行。

通过有参构造制定了Semaphore对象的许可证数量和获取许可证的方法为公平方式。

tryAcquire()是获取许可证的方法,该方法不会使线程阻塞,获取到许可证返回true,获取不到返回false。

release()获取许可证的线程在运行结束之后需释放许可证,为了保证许可证的释放,release()方法一般写在finally语句块中。

 

Semaphore原理分析

Semaphore的实现原理是什么的,接下来就和勾勾一起看源码吧。

Semaphore是基于AQS共享模式的实现,其维护的内部类实现了AbstractQueuedSynchronizer。

Semaphore在构造时指定许可证的数量即是指定了AQS同步器状态state的值,所以在Semaphore中state表示许可证的数量。

同时Semaphore构造时还可以指定获取许可证的公平或者非公平的方式,默认是非公平的获取方式,

Semaphore重写了共享模式下获取许可tryAcquireShared()和释放许可tryReleaseShared()方法,其内部类NonfairSync和FairSync实现不同的tryAcquireShared方法。

内部类Sync源码注释如下:

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;
 //有参构造,permits即是state的值
    Sync(int permits) {
        setState(permits);
    }
 //获取许可证数量,即获取state同步器状态值
    final int getPermits() {
        return getState();
    }
 //非公平方式获取操作
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            //获取剩余许可证数量
            int available = getState();
            //将剩余许可证减去当前线程想要获取的许可证数量
            int remaining = available - acquires;
            //如果小于0,则返回剩余的许可证数量,是个负值
            //如果不小于0,则通过CAS修改state的值,修改成功则返回剩余数量,是个正值
            //如果CAS失败,则一直循环
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
 //共享方式释放操作
    protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            //获取state的值
            int current = getState();
            //将state的值加上当前线程要释放的许可数量
            int next = current + releases;
            //如果释放之后的许可数量小于当前数量,则抛异常
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            //cas修改state的值,修改成功返回true,失败返回false
            if (compareAndSetState(current, next))
                return true;
        }
    }
}

NonfairSync非公平类,其获取许可的操作直接调用了父类Sync的nonfairTryAcquireShared()方法。

/**
 * NonFair version
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

    NonfairSync(int permits) {
        super(permits);
    }

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

FairSync公平类,其自己实现了tryAcquireShared()方法。

static final class FairSync extends Sync {
    private static final long serialVersionUID = 2014338818796000944L;

    FairSync(int permits) {
        super(permits);
    }

    protected int tryAcquireShared(int acquires) {
        for (;;) {
            //判断当前线程节点是否为下一个要唤醒的节点,不是则返回-1
            if (hasQueuedPredecessors())
                return -1;
            //如果是下一个要唤醒的节点,则尝试获取许可
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

我们之前《AQS之共享模式》一文中学习了AQS共享模式的原理,我们了解到tryAcquireShared()方法返回小于0的数值则表示线程获取许可失败,返回大于0的数值则表示线程许可成功。

获取失败之后会怎么样呢?

《AQS之共享模式》给你答案喔!

Semaphore基本流程:

图片

Semaphore方法总结

  • Semaphore构造器

    Semaphore(int permits):定义Semaphore指定许可数量,并且默认非公平的方式获取许可。

    Semaphore(int permits, boolean fair):定义Semaphore指定许可数量以及同步方式。

  • 获取方法

    acquire():当前线程调用此方法获取许可证,获取不到会进入阻塞状态一直等待,直到Semaphore有可用的许可或者被其他线程打断。如果Semaphore有可用的许可,该方法会立即返回。

    acquire(int permits):当前线程调用该方法会向Semaphore获取指定permits的许可数量,permits不能小于0。该方法作用与acquire()一样,只是指定了获取的许可数量,acquire()是获取一个许可数量。

    acquireUninterruptibly():当前线程调用此方法获取许可,如果获取不到会一直阻塞而且不能被打断,直到Semaphore有可用许可才能退出阻塞。此方法一般不用,因为可能会导致大规模的线程阻塞而导致Java进程假死现象。

    acquireUninterruptibly(int permits):该方法与acquireUninterruptibly()作用一样,只是可以指定获取permits个许可数量,permits不能小于0。

    tryAcquire():尝试获取许可数量,只会向Semaphore申请一个许可数量,如果Semaphore的许可数量大于等于1,将会获取成功返回true,否则将会获取许可失败返回false。

    tryAcquire(long timeout, TimeUnit unit):尝试获取许可数量,也是只申请一个许可,但是它增加了超时参数。如果在超时的时间内还是没有可用的许可,当前线程会进入阻塞状态,直到达到超时时间或者超时时间内获得了许可,或者阻塞线程被其他线程打断。

    tryAcquire(int permits):该方法与tryAcquire()作用一样,只是可以指定获取permits个许可数量,permits不能小于0。

    tryAcquire(int permits, long timeout, TimeUnit unit):该方法可以获取permits个许可数量,并且可以设置超时时间。

  • 释放操作

    release():释放一个许可证,Semaphore内部的许可证数量会加1,表示多了1个可用的许可。

    release(int permits):释放permits个许可证,Semaphore内部的许可证数量会加permits。permits不能小于0。

     

Semaphore总结

Semaphore可以允许一定数量的线程对共享资源进行访问,并且提供了丰富的获取操作:阻塞或者不阻塞,中断或者不中断、获取多个许可或者一个许可。

Semaphore虽然可以控制并发的线程数量,但是对共享资源的数据安全并没有提供任何保证,所以如果涉及到了共享可变资源的并发需要额外的控制。

如果在开发中使用了不阻塞的方式获取许可数量,需对返回结果进行处理,否则可能出现尝试获取许可失败却依然执行了业务逻辑。

好了今天就到这里,祝福大家周末愉快 !!!

下一篇文章是最后一个JUC工具类Phaser,哇哇哇,看到光明了!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值