• 多线程之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来限制连接数。

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

  • 相关阅读:
    Spring Data JPA 的作用.
    JavaEE 规范和 SSH 三大框架的关系
    一些疑惑
    Linux学习总结(21)——CentOS7环境下FTP服务器的安装和配置
    Docker学习总结(18)——阿里超大规模Docker化之路
    Maven学习总结(32)——Maven项目部署到Tomcat8中
    Maven学习总结(31)——Maven坐标详解
    猎豹CEO傅盛:与周鸿祎、雷军、马化腾、马云的的相爱相杀
    Java Web学习总结(32)——Java程序员最亲睐的Web框架
    VMWare学习总结(1)——Centos7安装完毕后无法联网的解决方法
  • 原文地址:https://www.cnblogs.com/caoleiCoding/p/15015067.html
Copyright © 2020-2023  润新知