Java并发编程三大神器之Semaphore

1、Semaphore是什么

Semaphore 是一个计数信号量,是JDK1.5引入的一个并发工具类,位于java.util.concurrent包中。可以控制同时访问资源的线程个数。Semaphore机制是提供给线程抢占式获取许可,所以他可以实现公平或者非公平,类似于ReentrantLock。

《Java并发编程艺术》中说:Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。

举个栗子:我们去停车场停车,停车场总共只有5个车位,但是现在有8辆汽车来停车,剩下的3辆汽车要么等其他汽车开走后进行停车,要么去其他停车场找别的停车位。

举个栗子:公园里面的厕所只有4个坑,假如有10个人要上厕所的话,那么同时能去上厕所的人就只能有4个,还剩下6个人只能在外面等待。当4个人中有任何一个人离开后,其中在等待的人中才有一个人可以继续使用,依次下去,直到所有人都上完厕所。

2、Semaphore小试牛刀

Semphore计数信号量由 new Semaphore(N) 指定数量N的 “许可” 初始化。每调用一次 acquire(),一个许可会被调用线程取走。每调用一次 release(),一个许可会被返还给信号量。因此,在没有任何 release() 调用时,最多有 N 个线程能够通过 acquire() 方法,N是该信号量初始化时的许可的指定数量。这些许可只是一个简单的计数器。

如下的例子,有一个商场拥有4个车位的停车场,有10辆车需要停车,当停进4辆车后,其余的6辆车就需要等待已经停进去的车开出去,才可以再停进去。

public class ParkingLot extends Thread {
    // 信号量
    private Semaphore semaphore;
    // 同时允许多少个线程同时执行
    private int num;

    public ParkingLot(Semaphore semaphore, int num) {
        this.semaphore = semaphore;
        this.num = num;
    }

    @Override
    public void run() {
        try {
            //汽车驶入停车场,需要获取一个许可
            semaphore.acquire();
            System.out.println(LocalDateTime.now() + " 第" + this.num + "辆汽车驶入停车场...");
            Thread.sleep(2000);
            System.out.println(LocalDateTime.now() + " 第" + this.num + "辆汽车驶出停车场>>>");//汽车驶出停车场,会释放一个许可
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ParkingLotSemaphoreDemo {
    // 停车场总共有5个车位
    private static final int MAX_AVAILABLE = 4;
    
    public static void main(String[] args) {
        //只有5个车位,就是说同时只能允许5辆汽车进行停车
        Semaphore semaphore = new Semaphore(MAX_AVAILABLE);
        //模拟10辆车驶入停车场
        for (int i = 1; i <= 10; i++) {
            new ParkingLot(semaphore, i).start();
        }
    }
}

在这里插入图片描述

3、Semaphore和CountDownLatch组合使用

比如说

public class SemaphoreTest {
    public static void main(String[] args) throws InterruptedException {
        // 初始化五个车位
        Semaphore semaphore = new Semaphore(5);
        // 等所有车子
        final CountDownLatch latch = new CountDownLatch(8);
        for (int i = 1; i <= 8; i++) {
            int finalI = i;
            if (i == 5) {
                Thread.sleep(1000);
                new Thread(() -> {
                    stopCarNotWait(semaphore, finalI);
                    latch.countDown();
                }).start();
                continue;
            }
            new Thread(() -> {
                stopCarWait(semaphore, finalI);
                latch.countDown();
            }).start();
        }
        latch.await();
        log("总共还剩:" + semaphore.availablePermits() + "个车位");
    }
	// 停车 等待车位
    private static void stopCarWait(Semaphore semaphore, int finalI) {
        String format = String.format("车牌号%d", finalI);
        try {
            semaphore.acquire(1);
            log(format + "找到车位了,去停车了");
            Thread.sleep(10000);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            semaphore.release(1);
            log(format + "开走了");
        }
    }
	// 不等待车位 去其他地方停车 
    private static void stopCarNotWait(Semaphore semaphore, int finalI) {
        String format = String.format("车牌号%d", finalI);
        try {
            if (semaphore.tryAcquire()) {
                log(format + "找到车位了,去停车了");
                Thread.sleep(10000);
                log(format + "开走了");
                semaphore.release();
            } else {
                log(format + "没有停车位了,不在这里等了去其他地方停车去了");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
	// 日志
    public static void log(String content) {
        // 格式化
        DateTimeFormatter fmTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // 当前时间
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now.format(fmTime) + "  " + content);
    }
}

在这里插入图片描述

从输出结果可以看到车牌号6这辆车一看没有车位了,就不在这个地方傻傻的等了,而是去其他地方了,但是车牌号7车牌号8分别需要等到车库开出2辆车空出2个车位后才停进去。这就体现了 Semaphoreacquire() 方法如果没有获取到凭证它就会阻塞,而 tryAcquire() 方法如果没有获取到凭证不会阻塞的。

4、Semaphore常用方法

public Semaphore(int permits):创建一个信号量,参数permits表示许可数目,即同时可以允许多少线程进行访问。默认采用的是非公平的策略。
public Semaphore(int permits, boolean fair):创建一个信号量,参数permits表示许可数目,即同时可以允许多少线程进行访问。多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可。

public void acquire():用于获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。【会阻塞】
public void acquire(int permits):用于获取permits个许可,若无许可能够获得,则会一直等待,直到获得许可。【会阻塞】

public void release():用于释放一个许可。在释放许可之前,得先获得许可。【会阻塞】
public void release(int permits):用于释放permits个许可。在释放许可之前,得先获得许可。【会阻塞】

public boolean tryAcquire():尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false。【不会阻塞】
public boolean tryAcquire(long timeout, TimeUnit unit):尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false。【不会阻塞】
public boolean tryAcquire(int permits):尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false。【不会阻塞】
public boolean tryAcquire(int permits, long timeout, TimeUnit unit):尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false。【不会阻塞】

public int availablePermits():用于获取信号量中当前可用的许可数目。

5、Semaphore 结语

  • 当信号量Semaphore初始化设置许可证为 1 时,它也可以当作互斥锁使用。其中 0、1 就相当于它的状态,当=1时 表示其他线程可以获取;当=0时 排他,即其他线程必须要等待。
  • Semaphore是JUC包中的一个很简单的工具类,用来实现多线程下对于资源的同一时刻的访问线程数限制
  • Semaphore中存在一个【许可】的概念,即访问资源之前,先要获得许可,如果当前许可数量为0,那么线程阻塞,直到获得许可。
  • Semaphore内部使用AQS实现,由抽象内部类Sync继承了AQS。因为Semaphore天生就是共享的场景,所以其内部实际上类似于共享锁的实现。
  • 共享锁的调用框架和独占锁很相似,它们最大的不同在于获取锁的逻辑——共享锁可以被多个线程同时持有,而独占锁同一时刻只能被一个线程持有。
  • 由于共享锁同一时刻可以被多个线程持有,因此当头节点获取到共享锁时,可以立即唤醒后继节点来争锁,而不必等到释放锁的时候。因此,共享锁触发唤醒后继节点的行为可能有两处,一处在当前节点成功获得共享锁后,一处在当前节点释放共享锁后。
  • 采用semaphore来进行限流的话会产生突刺现象。【突刺现象:指在一定时间内的一小段时间内就用完了所有资源,后大部分时间中无资源可用。比如在限流方法中的计算器算法,设置1s内的最大请求数为100,在前100ms已经有了100个请求,则后面900ms将无法处理请求。

参考:https://cloud.tencent.com/developer/article/1801558

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值