关于Semaphore的一点理解

深入理解Semaphore
本文详细解析了Semaphore的概念及其在控制线程并发数量中的作用,通过示例代码展示了acquire和release方法的使用,以及如何通过tryAcquire方法实现线程的舍弃策略。

Semaphore是AQS一个很重要的组件,之前对这块的认知有一点偏差,给自己埋了个坑,特意梳理一下。

1.Semaphore概念

Semaphore:即信号量,可以控制一组并发访问的线程的数目。核心方法acquire()和release(),acquire()是获取一个许可,如果没有足够的就等待,直到获取许可,release()是在操作完成后释放一个许可,如果没有release方法,到后面就会导致许可不够,其他的线程就一直无法得到许可,后面所有的线程就会阻塞在那里。下面是一个小demo,帮助理解

		ExecutorService exec = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(3);   //通道允许三个许可
        for(int i = 0;i<20;i++){
            final int threadNum = i;
            exec.execute(()->{
                try {
                    semaphore.acquire();    //获取一个许可,这里 可以更改参数同时获取多个许可,比如获取三个许可:semaphore.acquire(3); 
                    ...
                    semaphore.release();    //释放一个许可,同样的这里也可以设置释放的许可数
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
            });
        }
        exec.shutdown();

Semaphored的构造方法还可设置第二个boolean类型的参数isFair , 如下:

final Semaphore semaphore = new Semaphore(3,true); 

当boolean值为true时表示采取公平策略,false采取非公平策略,在公平策略中如果前面有线程在等待资源,该线程就会被放入FIFO等待队列,获取许可失败。但是非公平策略会抢占资源,即可能存在线程B获得了刚刚释放的许可,但是它前面还有线程A在等待。注意如果不设置,就默认采取非公平策略

2.tryAcquire()方法

如果业务并发量实在太大,为了控制并发需要舍弃部分线程,我们可通过tryAcquire()方法实现,下面是一个demo,只允许三个线程执行test()方法,这里同时有3个线程可以获得许可,但是每个获得许可的线程执行test()方法会sleep1秒,所以剩下的线程就获取不到许可,全部被放弃了

@Slf4j
public class SemaphoreDemo {

    private static final int threadCount = 10;
    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(3);
        for(int i = 0;i<threadCount;i++){
            final int threadNum = i;
            exec.execute(()->{
                try {
                   if(semaphore.tryAcquire()){  //尝试获取一个许可,如果获取到了许可,就继续执行,否则就丢弃掉。
                       test(threadNum);
                       semaphore.release();    //释放许可
                   }
               } catch (InterruptedException e) {
                    log.error("exception",e);
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int threadNum) throws InterruptedException {
        log.info("{}", threadNum);
        Thread.sleep(1000);
    }
}

tryAcquire()默认尝试获取一个许可,如果需要获取多个,可设置参数,如下,尝试获取三个许可:

semaphore.tryAcquire(3);

tryAcquire()方法默认不等待,如果获取不到许可,就立即放弃当前线程,可通过设置时间参数设置等待时间,如下的demo表示等待一秒钟,如果一秒内获取不到许可就拉倒,直接放弃

semaphore.tryAcquire(1,TimeUnit.SECOND)//第一个参数传时间数值,第二个参数传时间单位,这里合起来表示1秒

当然你也可以同时设置等待时间和请求的许可数:

semaphore.tryAcquire(3,1,TimeUnit.SECOND)//结合了第二个和第三个,尝试获取三个许可,等1秒取不到就拉倒
<think>嗯,用户想了解Java集合类中的Semaphore以及相关方法。首先,我需要确认Semaphore是否属于Java集合框架的一部分。Semaphore实际上是java.util.concurrent包下的一个并发工具类,虽然它不属于传统的集合类,但有时会被归类在并发集合的讨论中。可能用户在这里有些混淆,但需要明确指出这一点。 接下来,得解释Semaphore的基本概念。Semaphore是一个计数信号量,用于控制同时访问特定资源的线程数量。它的核心思想是许可证(permits),线程需要获取许可证才能执行,执行完毕后释放许可证。 然后,需要介绍Semaphore的构造方法。主要有两个构造方法:一个是指定许可证数量的Semaphore(int permits),另一个是带有公平性参数的Semaphore(int permits, boolean fair)。公平性参数决定了等待线程获取许可证的顺序,true表示按等待顺序分配,避免线程饥饿。 接下来是常用方法。acquire()方法用于获取许可证,如果没有可用的会阻塞;release()用于释放许可证。还有tryAcquire(),可以非阻塞或带超时地尝试获取许可证,以及availablePermits()查看当前可用许可证数量,reducePermits()减少许可证等。 用户可能需要知道如何使用Semaphore解决实际问题,比如资源池管理或限流。举个例子,比如数据库连接池,限制同时使用的连接数。这时候,Semaphore的许可证数量就对应连接数,线程在使用前acquire,用完后release。 另外,要注意可能出现的误区,比如acquire()和release()的调用次数需要匹配,否则许可证数量会异常,导致资源泄露或过度使用。还要提醒在使用时考虑公平性对性能的影响,通常非公平模式吞吐量更高,但公平模式避免饥饿。 用户可能还想了解Semaphore和其他并发工具的区别,比如和CountDownLatch或CyclicBarrier的不同。Semaphore侧重于控制访问资源的线程数,而后者更侧重于线程间的同步。 最后,给出一个简单明了的代码示例,比如控制最多3个线程同时执行,这样用户能直观看到Semaphore的效果。示例中展示acquire和release的正确使用,以及try-finally块确保释放,避免异常导致许可证未被释放的情况。 总结起来,回答需要结构清晰,先介绍概念,再讲构造方法,然后是常用方法,接着是使用场景和注意事项,最后给出实例。确保用户能逐步理解Semaphore的作用和使用方式,同时避免常见错误。</think>Java中的`Semaphore`是`java.util.concurrent`包下的一个并发工具类,用于控制对共享资源的并发访问线程数。虽然它不属于传统的集合类(如`List`、`Map`),但常用于解决资源池管理、流量控制等场景。 --- ### 一、Semaphore核心概念 1. **信号量机制** Semaphore通过维护一组**许可证(permits)**实现资源控制: - 线程通过`acquire()`获取许可证(若无可用则阻塞) - 使用完资源后通过`release()`释放许可证 2. **核心特性** - 限制同时访问资源的线程数量 - 支持公平/非公平模式(避免线程饥饿) --- ### 二、构造方法 1. **默认非公平模式** ```java Semaphore semaphore = new Semaphore(int permits); ``` 2. **指定公平性** ```java Semaphore semaphore = new Semaphore(int permits, boolean fair); ``` - `permits`: 初始许可证数量 - `fair`: `true`表示按等待顺序分配许可证 --- ### 三、关键方法 | 方法 | 说明 | |------|------| | `void acquire()` | 获取1个许可证,阻塞直到可用 | | `void acquire(int permits)` | 获取多个许可证 | | `void release()` | 释放1个许可证 | | `void release(int permits)` | 释放多个许可证 | | `boolean tryAcquire()` | 尝试获取1个许可证(立即返回) | | `boolean tryAcquire(long timeout, TimeUnit unit)` | 带超时的尝试获取 | | `int availablePermits()` | 返回当前可用许可证数量 | | `void reducePermits(int reduction)` | **减少许可证数量(线程不安全)** | --- ### 四、典型使用场景 #### 示例:控制最多3个线程同时访问资源 ```java Semaphore semaphore = new Semaphore(3); // 初始化3个许可证 Runnable task = () -> { try { semaphore.acquire(); // 获取许可证 // 执行受保护的资源操作 System.out.println(Thread.currentThread().getName() + "正在使用资源"); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); // 必须释放许可证 } }; // 启动多个线程测试 for (int i = 0; i < 10; i++) { new Thread(task).start(); } ``` **输出结果**:始终最多有3个线程同时打印信息 --- ### 五、注意事项 1. **acquire与release必须成对调用** 避免资源泄漏(忘记释放)或超额释放(导致许可证数量超过初始值) 2. **公平模式性能影响** 公平模式保证顺序但降低吞吐量,非公平模式反之 3. **动态调整许可证** 通过`reducePermits()`可动态减少许可证,但需注意线程安全问题 --- ### 六、扩展对比 | 工具 | 用途 | |------|------| | `Semaphore` | 控制并发线程数量 | | `CountDownLatch` | 等待多个任务完成 | | `CyclicBarrier` | 多线程相互等待到同步点 | 如需更深入讨论具体使用场景或底层实现,可以进一步补充说明!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值