多线程之Semaphore

前言

最近一段时间,我们一直都是在分享多线程相关的知识点,截止到今天我们已经分享过锁、计数器等相关知识,主要分享了一些常用的多线程控制方式,今天我们来继续分享另一个多线程控制组件——Semaphore

Semaphore

示例代码

Semaphore也是jdk1.5引入的组件,它的字面意思是信号量,但是单从字面翻译我们是无法得知它的作用的,根据官方注释以及网上的一些解释,semaphore简单来说就是多线程运行控制器,它的主要作用就是控制统一时间并发的线程数量,从这一特性上看,它最适用的场景就是限流,下面我们通过一段简单的代码来说明它的用法:

public class Example {
    private static final int THREAD_SIZE = 30;
    private static final Semaphore s = new Semaphore(5);

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_SIZE);
        for (int i = 0; i < THREAD_SIZE*2; i++) {
            int finalI = i;
            executorService.execute(() -> {
                try {
                    s.acquire();
                    System.out.println(Thread.currentThread().getName() + " currentTimeMillis: "+ System.currentTimeMillis());
                    Thread.sleep(2000);
                    System.out.println("thread count: " + finalI);
                    s.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        executorService.shutdown();
    }
}

简单说明

在上面的代码中,我们定义了一个线程池,线程池大小为30,同时我们还定义了一个线程运行控制器,控制器的大小我们设定为5,也就是说,在当前控制器的作用下,同一时间只允许5个线程运行;

在线程内部,在线程运行最开始,我们通过Semaphoreacquire方法获取运行许可(拿不到运行凭证是无法运行的),你也可以通过tryAcquire方法获取运行凭证,两个方法的区别是tryAcquire有一个布尔的返回值,是非阻塞的,而acquire是阻塞的,如果拿不到会一直等,关于这一点,官方文档说的很清楚:

线程内部我们休眠了两秒,然后通过release方法释放Semaphore资源,这样其他的线程才能拿到这个凭证。

最后,我们用一个for循环来运行线程,循环次数我们设定为线程池大小的两倍,然后我们运行上面的代码,运行结果大致如下:

从运行结果中我们可以看到,虽然线程池大小是30,但是同一时间运行的线程只有5个,也就是我们Semaphore的初始化大小。我们可以试着把Semaphore的大小修改下看下运行结果:

初始化大小为10

同一时间有10个线程在运行

初始化大小为2

同一时间有2个线程在运行。

作用范围

下面我们把代码做一些简单调整:

我们分别在acquire方法前和release方法后加一行代码,这时候我们的semaphore初始化还是2,然后运行下:

我们发现,虽然acquire方法前和release方法之间以及之后的代码虽然同一时间只有两个线程在运行,但是之前的代码同一时间是有多于两个线程在运行的,这就是说Semaphore只会影响acquire方法前和release方法之间和之后区域的线程并发数,影响之后的代码是因为acquire方法是阻塞的,如果我们换成tryAcquire应该就是另外一番场景了:

然后运行:

根据运行结果我们发现,这时候acquire方法前和release方法之间以及之后的代码都不受限制了,都出现了并发数超过限制数的情况。具体原因,我们前面说了,tryAcquire方法是非阻塞的,所以这时候我们需要人为根据tryAcquire来控制代码逻辑,比如直接结束或者进行其他操作:

 boolean b = s.tryAcquire();
if (!b) {
    // doSomething()
    System.out.println("未拿到访问权限");
    return;
}

总结

如果说我们之前讲的锁,是单线程锁(同一时间只运行一个线程访问),那么Semaphore更像是一个多线程锁(同一时间允许多个指定线程访问),它也很像我们之前说的令牌桶(限流解决方案),凭令牌数据访问相关服务,与之不同的是,这里的Semaphore可以重复使用的,所以它同样可以用于限流,比如获取数据库连接,我们可以通过Semaphore来限制连接数。

好了,今天的内容就到这里,更多其他的应用场景就需要各位小伙伴根据自己的需求积极探索,大胆尝试了。

原文地址:https://www.cnblogs.com/caoleiCoding/p/15015067.html