semaphore学习

1、semaphore概述

semaphore可以看作是一个许可的集合,用于控制多个线程对有限共享资源的访问。当某个线程需要访问资源时,必须首先从semaphore获取一个许可,使用完资源后再将许可归还给semaphore。

2、semaphore常用方法

控制共享资源的多线程访问

(1)初始化许可数量
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

(2)获得许可
available.acquire(); // 从Semaphore获取【一个】许可,成功则立即返回,并将剩余许可数减1,如果没有可用许可则阻塞直至拿到可用许可
available.acquire(int num); // 一次获取【多个】
available.acquireUninterruptibly(); // 与acquire()区别是当线程发生中断后,是否还继续等待获取许可
available.tryAcquire(); // 与acquire()区别是当前没有可用许可时,是否阻塞
available.tryAcquire(timeout, unit); // 不阻塞,在指定时间内没有可用许可则直接返回false

(3)归还许可
available.release(); // 归还【一个】许可给Semaphore,并将许可数加1
available.release(int num); // 归还【多个】

注意:release()释放的许可不一定是acquire()获取的许可

(4)查询可用许可数量
available.availablePermits();

当做互斥锁使用

private final Semaphore available = new Semaphore(1);
当许可只有1个时,可以实现类似synchronized或lock的功能,任何时候只能有一个线程操作共享资源

支持公平与非公平模式

等待时间长的线程是否优先获得许可

3、semaphore应用

控制客户端在单位时间内调用某个远程服务不超过N次

public class TestSemaphore2 {

    final static int MAX_QPS = 10;
    final static Semaphore semaphore = new Semaphore(MAX_QPS);

    public static void main (String ... args) throws Exception {
        Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                semaphore.release(MAX_QPS-semaphore.availablePermits()); // 防止许可一直递增
                System.out.println("--------------------------");
            }
        }, 5000, 5000, TimeUnit.MILLISECONDS);

        //lots of concurrent calls:100 * 1000
        ExecutorService pool = Executors.newFixedThreadPool(100);
        for (int i=100;i>0;i--) {
            final int x = i;
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j=1000;j>0;j--) {
                        semaphore.acquireUninterruptibly(1);
                        remoteCall(x, j);
                    }
                }
            });
        }

        pool.shutdown();
        pool.awaitTermination(1, TimeUnit.HOURS);
        System.out.println("DONE");
    }

    private static void remoteCall(int i, int j) {
        System.out.println(String.format("%s - %s: %d %d",new Date(),
            Thread.currentThread(), i, j));
    }
}

4、参考
http://ifeve.com/concurrency-practice-1/

<think>好的,我现在需要帮助用户了解Java中Semaphore的用法,并提供教程及示例。首先,我要回忆Semaphore的基本概念。Semaphore是Java并发包中的一个同步工具,用来控制同时访问共享资源的线程数量。它通过许可证机制来管理,线程获取许可证才能访问资源,使用完后释放许可证。 接下来,用户可能需要知道如何创建Semaphore。根据引用[3],构造方法有两种:指定许可数,或者指定许可数和公平性。公平性设置为true时,线程按请求顺序获取许可,避免饥饿现象。 然后,用户会关心核心方法。acquire()用于获取许可,release()用于释放。还有tryAcquire()可以非阻塞尝试获取,这些方法可能需要处理中断异常,所以得提醒用户注意异常处理。 接下来,我应该考虑实际应用场景。比如数据库连接池,限制同时访问的连接数。或者限流,控制接口的并发请求量。这些例子能让用户更容易理解Semaphore的作用。 然后,我需要编写示例代码。基础示例展示如何创建Semaphore,线程获取和释放许可。另一个示例模拟资源池,比如连接池,使用Semaphore管理可用连接数量。代码要简洁,加上注释说明每一步的作用。 还要注意许可证的动态调整,比如在运行时增减许可数,这在引用[3]中提到过。另外,异常处理也很重要,比如acquire()可能被中断,需要捕获InterruptedException。 用户可能还会问及底层原理,但根据问题,主要关注使用教程,所以暂时不需要深入原理,但可以提到Semaphore基于AQS实现,维护许可证的状态,这样用户有兴趣可以自行查阅。 最后,生成相关问题,帮助用户进一步学习,比如与CountDownLatch的区别,如何处理中断异常,以及性能优化的方法。这些问题引导用户深入探索相关知识。 现在需要确保回答结构清晰,分步骤讲解,代码示例正确,符合用户的中文要求,并且正确引用提供的参考资料。同时检查LaTeX格式是否正确,行内公式用$...$,独立公式用$$...$$,但在这个问题中可能涉及较少数学公式,所以可能不需要太多LaTeX。但如果有提到资源数量等,可能需要用变量表示,比如$n$个许可。 总结回答的结构应该是:简介、核心方法、使用步骤、示例代码、应用场景、注意事项、相关问题。确保每个部分都涵盖必要的信息,并且引用对应的参考资料,比如创建Semaphore时引用[3],许可证机制引用[4]。</think>### Java Semaphore 使用教程及示例 #### 1. Semaphore 简介 Semaphore(信号量)是 Java 并发编程中用于控制多线程对共享资源访问的同步工具。其核心是一个计数器,表示可用许可数(Permits)。线程通过获取许可访问资源,使用后释放许可[^4]。 #### 2. 核心方法 - **`acquire()`**:获取一个许可(若无可用许可则阻塞)。 - **`release()`**:释放一个许可。 - **`tryAcquire()`**:非阻塞尝试获取许可,立即返回成功或失败。 - **`availablePermits()`**:返回当前剩余许可数[^2][^3]。 #### 3. 使用步骤 1. **创建 Semaphore** 指定初始许可数及是否公平: ```java Semaphore semaphore = new Semaphore(3); // 非公平模式,初始3个许可 Semaphore fairSemaphore = new Semaphore(3, true); // 公平模式 ``` 2. **获取与释放许可** 通过 `acquire()` 和 `release()` 控制资源访问: ```java try { semaphore.acquire(); // 获取许可 // 访问共享资源(如数据库连接、文件操作) } finally { semaphore.release(); // 确保释放许可 } ``` 3. **动态调整许可数** 运行时增减许可: ```java semaphore.release(2); // 增加2个许可 ``` #### 4. 示例代码 **示例1:基础用法** 模拟 5 个线程竞争 3 个许可: ```java import java.util.concurrent.Semaphore; public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore = new Semaphore(3); // 3个许可 for (int i = 0; i < 5; i++) { new Thread(() -> { try { semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " 获取许可"); Thread.sleep(2000); // 模拟操作耗时 } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); System.out.println(Thread.currentThread().getName() + " 释放许可"); } }, "线程" + i).start(); } } } ``` 输出结果: ``` 线程0 获取许可 线程1 获取许可 线程2 获取许可 线程0 释放许可 线程3 获取许可 线程1 释放许可 线程4 获取许可 ... ``` **示例2:资源池管理** 模拟数据库连接池(最多支持 5 个连接): ```java public class ConnectionPool { private static final int MAX_CONNECTIONS = 5; private final Semaphore semaphore = new Semaphore(MAX_CONNECTIONS); public Connection getConnection() throws InterruptedException { semaphore.acquire(); return createConnection(); // 实际创建连接的逻辑 } public void releaseConnection(Connection conn) { closeConnection(conn); // 实际关闭连接的逻辑 semaphore.release(); } } ``` #### 5. 应用场景 - **限流**:控制接口每秒最大并发请求数。 - **资源池**:管理数据库连接、线程池等。 - **生产者-消费者模型**:协调生产与消费速率。 #### 6. 注意事项 - **避免死锁**:确保 `acquire()` 后必有 `release()`,推荐在 `finally` 中释放。 - **公平性选择**:公平模式保证先进先出,但可能降低吞吐量。 - **动态调整许可数**:通过 `release(n)` 增加许可,但需谨慎避免逻辑漏洞。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值