天增的博客
首页
博客
  • 分布式解决方案
  • Java并发工具包
  • redis
  • LeetCode
  • 系统设计
  • JVM体系
Github (opens new window)
Rss (opens new window)
  • zh-CN
  • en-US
首页
博客
  • 分布式解决方案
  • Java并发工具包
  • redis
  • LeetCode
  • 系统设计
  • JVM体系
Github (opens new window)
Rss (opens new window)
  • zh-CN
  • en-US
  • Java并发工具包
  • 并发基础
    • 线程基础
      • Thread的状态
      • 进程与线程
      • 正确停止线程的方式
      • Thread的实现方式
      • waitnotifynotifyAll
      • 生产者消费者模型
    • 线程安全
      • 线程不安全
      • 线程安全
      • 需要注意线程安全问题的情况
  • 并发工具
    • 线程协作
      • Semaphore信号量
        • 介绍
        • 限流实现
        • 思考: FixedThreadPool可以替代Semaphore么?
      • CountDownLatch详解
      • 使用CompletableFuture解决旅游平台问题
      • 使用CyclicBarrier解决团建问题
    • Future
      • Future主要功能
      • FutureTask源码分析
    • ThreadLocal
      • ThreadLocal内存泄漏
      • ThreadLocal使用场景
    • 原子类
      • 原子类的作用概览
      • 原子类的性能分析
    • 阻塞队列
      • 常见的阻塞队列
      • 阻塞队列的常用方法
      • 什么是阻塞队列
    • 并发容器
      • HashMap
      • CopyOnWriteArrayList
      • ConcurrentHashMap详解
    • 线程池
      • 为什么多线程会带来性能问题
      • 线程池的优势
      • 创建线程池的参数
        • 如何设置线程数
      • 线程池线程复用原理
      • ForkJoin框架
    • 各种锁
      • 锁的种类和特点
        • 公平锁非公平锁
        • 自旋锁非自旋锁
        • 共享锁独占锁
        • 乐观锁和悲观锁
      • JVM锁优化
      • synchronized和Lock的对比
      • lock的常用方法
  • 底层原理
    • CAS原理
    • AQS框架
    • 伪共享
    • java内存模型
      • Java内存模型介绍
      • happens-before规则
  • topic
  • Java并发工具包
  • 并发工具
  • 线程协作
  • Semaphore信号量
2022-04-21
目录

Semaphore信号量

# Semaphore信号量

# 介绍

Semaphore,通常我们叫它信号量,用于控制同时访问特定资源的线程数量,用于协调各个线程,以确保合理利用资源。

如下图所示,Semaphore在图中充当一个保镖的角色,在同一时刻只允许放行3个线程进来,其他的线程就在外面排队。

Semaphore

Semaphore会维护一个计数器,用于标记许可证的计数,线程去访问共享资源之前,需要先获取许可证,这时Semaphore持有的信号量需要-1,只要持有的数量为0 ,这时新的线程想要继续访问受保护的资源时,只有等待,等到其他持有许可证的线程释放了手中的许可证。

利用这种特性,可以很容易做出限流的功能

# 限流实现

请求访问到我们的应用上面,而这个应用又依赖这个慢服务

慢服务

这个时候,就为了保证那个慢服务不被打垮,就需要对进来的请求进行一个限流的操作。

public class TestSemaphore2 {
    final static Semaphore semaphore = new Semaphore(3);
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(30);
        for (int i = 0; i < 1000; i++) {
            pool.submit(new Task());
        }
    }
    static class Task implements Runnable {
        @Override
        public void run() {
            try {
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿到了许可证");
            try {
                // 模拟业务耗时操作
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("慢服务执行完毕," + Thread.currentThread().getName() + "释放了许可证");
            semaphore.release();
        }
    }
}

这段代码中新建了一个数量为3的信号量,也就意味着同时只有3个线程可以访问我们的慢服务,其他线程则处于等待状态。

在线程最开始,通过acquire方法申请许可证(信号量持有的许可证会-1),然后进行业务耗时操作,执行完成之后通过release()方法释放许可证。

许可证为0的情况下,调用accquire会阻塞。

最后打印出来的结果:

pool-1-thread-2拿到了许可证
pool-1-thread-3拿到了许可证
pool-1-thread-4拿到了许可证
慢服务执行完毕,pool-1-thread-2释放了许可证
慢服务执行完毕,pool-1-thread-3释放了许可证
慢服务执行完毕,pool-1-thread-4释放了许可证
pool-1-thread-1拿到了许可证
pool-1-thread-6拿到了许可证
pool-1-thread-7拿到了许可证
慢服务执行完毕,pool-1-thread-6释放了许可证
慢服务执行完毕,pool-1-thread-7释放了许可证
慢服务执行完毕,pool-1-thread-1释放了许可证
....

通过运行结果可以看出,最多只有3个线程可以访问我们的慢服务

# 思考: FixedThreadPool可以替代Semaphore么?

如果我们把上面的例子,声明线程池的时候数量直接限死为3,是不是也能达到Semaphore的效果。

来尝试一下:

public class TestSemaphore2 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 1000; i++) {
            pool.submit(new Task());
        }
    }
    static class Task implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "拿到了许可证");
            try {
                // 模拟业务耗时操作
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("慢服务执行完毕," + Thread.currentThread().getName() + "释放了许可证");
        }
    }
}

执行结果:

pool-1-thread-1拿到了许可证
pool-1-thread-3拿到了许可证
pool-1-thread-2拿到了许可证
慢服务执行完毕,pool-1-thread-3释放了许可证
慢服务执行完毕,pool-1-thread-2释放了许可证
慢服务执行完毕,pool-1-thread-1释放了许可证
pool-1-thread-3拿到了许可证
pool-1-thread-1拿到了许可证
pool-1-thread-2拿到了许可证
慢服务执行完毕,pool-1-thread-1释放了许可证
pool-1-thread-1拿到了许可证
慢服务执行完毕,pool-1-thread-2释放了许可证
pool-1-thread-2拿到了许可证
慢服务执行完毕,pool-1-thread-3释放了许可证
pool-1-thread-3拿到了许可证
...

从执行结果看出来,好像和使用Semaphore的结果一样的。

其原因是,线程池最大就限制了3个线程,那么自然最多只有三个线程同时去访问。

那意味着,是不是固定数量的FixedThreadPool能够被Semaphore替代了呢?

答案是否定的。

因为在实际的业务场景中,Semaphore是搭配着某些限制条件来进行使用的。

就比如,每天秒杀系统,在秒杀的那个时刻我们只希望有少量的单子可以成功下单,但是在绝大部分的时候,我们就不应该限制,那么用线程池去控制灵活度就不高了。

此外,Semaphore作为一个类单独来使用,可以具备跨线程、跨线程池的特定,所以即便是在不同的线程、线程池中都发起请求,我们也能够限制访问的数量。

用FixedThreadPool去限制,那就做不到这么灵活控制,功能会大大的削弱。

‍

‍

最近更新
01
以 root 身份启动 transmission-daemon
12-13
02
Debian系统安装qbittorrent-nox
12-09
03
LXC Debain12安装zerotier并实现局域网自动nat转发
07-29
更多文章>
Theme by Vdoing | Copyright © 2015-2024 天增 | 苏ICP备16037388号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式