并发编程学习笔记 之 工具类Semaphore(信号量)

1、概念

  Semaphore(信号量)是一个线程同步工具,主要用于在一个时刻允许多个线程对共享资源进行并行操作的场景。通常情况下,使用Semaphore的过程实际上是多个线程获取访问共享资源许可证的过程。

  Semaphore内部逻辑:
在这里插入图片描述

  • 如果此时Semaphore内部的计数器大于零,那么线程将可以获得小于该计数器数量的许可证,同时还会导致Semaphore内部的计数器减少所发放的许可证数量。
  • 如果此时Semaphore内部的计数器等于0,也就是说没有可用的许可证,那么当前线程有可能会被阻塞(使用tryAcquire方法时不会阻塞)。
  • 当线程不再使用许可证时,需要立即将其释放以供其他线程使用,所以建议将许可证的获取以及释放动作写在try…finally语句块中。

2、Semaphore使用示例

  这里模拟最大人数的场景,当人数达到上限收,后续的线程就无法进入,当有信号量被释放后,后续的线程可以继续进入。

public class SemaphoreTest {

    public static void main(String[] args) {
        final LoginService loginService = new LoginService(5);
        for (int i=0;i<10;i++){
            Thread thread = new Thread(()->{
                if(loginService.login()){
                    try {
                        //模拟游玩时间
                        TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                        System.out.println(Thread.currentThread().getName() + ",开始游玩!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                       loginService.logout();
                    }
                }else{
                    System.out.println(Thread.currentThread().getName() + "人满,回家睡觉!");
                }
            });
            thread.start();
        }
    }

    private static class LoginService{

        private final Semaphore semaphore;

        public LoginService(int maxUser){
            this.semaphore = new Semaphore(maxUser,true);
        }
        public boolean login(){
            if(semaphore.tryAcquire()){
                System.out.println(Thread.currentThread().getName() + ",进园成功!");
                return true;
            }
            return false;
        }
        public void logout(){
            semaphore.release();
            System.out.println(Thread.currentThread().getName() + ",出园!");
        }
    }
}
Thread-0,进园成功!
Thread-1,进园成功!
Thread-2,进园成功!
Thread-3,进园成功!
Thread-4,进园成功!
Thread-5人满,回家睡觉!
Thread-6人满,回家睡觉!
Thread-3,开始游玩!
Thread-3,出园!
Thread-7,进园成功!
Thread-9人满,回家睡觉!
Thread-8人满,回家睡觉!
Thread-0,开始游玩!
Thread-0,出园!
Thread-1,开始游玩!
Thread-1,出园!
Thread-7,开始游玩!
Thread-7,出园!
Thread-4,开始游玩!
Thread-4,出园!
Thread-2,开始游玩!
Thread-2,出园!

  在上述示例中,线程“Thread-7”等到有是否资源后,又可以获取进入资格了。

  release方法: 在Semaphore设计中,并未强制要求执行release操作的线程必须是执行了acquire的线程才可以,而是需要开发人员自身具有相应的编程约束来确保Semaphore的正确使用,这样就可能造成不是获取到信号量的线程调用了release方法,从而造成信号量的错误释放。错误示例如下,本来主线程不应该获取到信号量,但是实际上因为t2线程的中断,执行了finally 的中的release方法,造成了信号量的错误释放。

public class Semaphore2Test {

    public static void main(String[] args) throws InterruptedException {
        //定义信号量
        final Semaphore semaphore = new Semaphore(1,true);

        Thread t1 = new Thread(()->{
            try{
                semaphore.acquire();
                System.out.println("t1 获取到资源!");
                TimeUnit.HOURS.sleep(1);
            }catch (Exception e){
                System.out.println("t1 当前线程被中断!");
            }finally {
                semaphore.release();
            }
        });
        Thread t2 = new Thread(()->{
            try{
                semaphore.acquire();
                System.out.println("t2 获取到资源!");
            }catch (Exception e){
                System.out.println("t2 当前线程被中断!");
            }finally {
                semaphore.release();
            }
        });
        t1.start();
        TimeUnit.SECONDS.sleep(2);
        t2.start();
        TimeUnit.SECONDS.sleep(2);
        t2.interrupt();
        semaphore.acquire();
        System.out.println("主线程获取资源访问权限!");
    }
}

3、Semaphore常用方法

1、构造函数
  有两个构造函数。其中,所谓的公平信号量是获得锁的顺序与线程启动顺序有关,但不代表100%地获得信号量,仅仅是在概率上能得到保证。而非公平信号量就是无关的。

  • public Semaphore(int permits):定义Semaphore指定许可证数量,并且指定非公平的同步器,因此new Semaphore(n)实际上是等价于new Semaphore(n,false)的。
  • public Semaphore(int permits, boolean fair):定义Semaphore指定许可证数量的同时给定非公平或是公平同步器。

2、tryAcquire方法
  tryAcquire方法尝试向Semaphore获取许可证,如果此时许可证的数量少于申请的数量,则对应的线程会立即返回,结果为false表示申请失败,tryAcquire包含如下:

  • tryAcquire():尝试获取Semaphore的许可证,该方法只会向Semaphore申请一个许可证,在Semaphore内部的可用许可证数量大于等于1的情况下,许可证将会获取成功,反之获取许可证则会失败,并且返回结果为false。
  • boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException:该方法与tryAcquire无参方法类似,同样也是尝试获取一个许可证,但是增加了超时参数。如果在超时时间内还是没有可用的许可证,那么线程就会进入阻塞状态,直到到达超时时间或者在超时时间内有可用的证书(被其他线程释放的证书),或者阻塞中的线程被其他线程执行了中断。
  • boolean tryAcquire(int permits):在使用无参的tryAcquire时只会向Semaphore尝试获取一个许可证,但是该方法会向Semaphore尝试获取指定数目的许可证。
  • boolean tryAcquire(int permits, long timeout, TimeUnit unit):该方法与第二个方法类似,只不过其可以指定尝试获取许可证数量的参数

3、acquire方法
  acquire方法也是向Semaphore获取许可证,但是该方法比较偏执一些,获取不到就会一直等(陷入阻塞状态),但是可以被其他线程中断,Semaphore为我们提供了acquire方法的两种重载形式:

  • void acquire():该方法会向Semaphore获取一个许可证,如果获取不到就会一直等待,直到Semaphore有可用的许可证为止,或者被其他线程中断。当然,如果有可用的许可证则会立即返回。
  • void acquire(int permits):该方法会向Semaphore获取指定数量的许可证,如果获取不到就会一直等待,直到Semaphore有可用的相应数量的许可证为止,或者被其他线程中断。同样,如果有可用的permits个许可证则会立即返回。

4、acquireUninterruptibly方法
  和acquire方法相比,不会被其他线程中断。

  • void acquireUninterruptibly():该方法会向Semaphore获取一个许可证,如果获取不到就会一直等待,与此同时对该线程的任何中断操作都会被无视,直到Semaphore有可用的许可证为止。当然,如果有可用的许可证则会立即返回。
  • void acquireUninterruptibly(int permits):该方法会向Semaphore获取指定数量的许可证,如果获取不到就会一直等待,与此同时对该线程的任何中断操作都会被无视,直到Semaphore有可用的许可证为止,或者被其他线程中断。同样,如果有可用的permits个许可证则会立即返回。

5、release方法
  release方法需要正确使用,避免出现上述提到的情况。

  • void release():释放一个许可证,并且在Semaphore的内部,可用许可证的计数器会随之加一,表明当前有一个新的许可证可被使用。
  • void release(int permits):释放指定数量(permits)的许可证,并且在Semaphore内部,可用许可证的计数器会随之增加permits个,表明当前又有permits个许可证可被使用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

姠惢荇者

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值