Semaphore 使用及原理详解

Semaphore 使用及原理详解

一、Semaphore 是什么?

Semaphore(信号量) 是 Java 并发包中的一个同步工具类,用于控制同时访问特定资源的线程数量。它通过维护一组许可(permits) 来实现流量控制。

核心概念

  • 许可(Permits):代表可用的资源数量

  • 获取许可(acquire):线程需要获取许可才能访问资源

  • 释放许可(release):线程使用完资源后释放许可

二、基本使用

1. 构造函数

java 复制代码
// 创建具有指定许可数的信号量
Semaphore semaphore = new Semaphore(3);

// 创建具有指定许可数并指定公平性的信号量
Semaphore semaphore = new Semaphore(3, true); // true 表示公平锁

2. 基本方法

java 复制代码
public class SemaphoreDemo {
    public static void main(String[] args) {
        // 创建信号量,只有3个许可
        Semaphore semaphore = new Semaphore(3);
        
        // 创建10个线程,但只能有3个同时执行
        for (int i = 1; i <= 10; i++) {
            new Thread(new Worker(i, semaphore)).start();
        }
    }
    
    static class Worker implements Runnable {
        private int id;
        private Semaphore semaphore;
        
        public Worker(int id, Semaphore semaphore) {
            this.id = id;
            this.semaphore = semaphore;
        }
        
        @Override
        public void run() {
            try {
                // 获取许可(如果没有可用许可则阻塞)
                semaphore.acquire();
                System.out.println("工人" + id + "开始工作");
                
                // 模拟工作
                Thread.sleep(2000);
                
                System.out.println("工人" + id + "完成工作");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 释放许可
                semaphore.release();
            }
        }
    }
}

三、主要方法详解

1. 获取许可的方法

java

复制代码
// 获取一个许可,如果无许可可用则阻塞
semaphore.acquire();

// 获取指定数量的许可
semaphore.acquire(2);

// 尝试获取许可,立即返回结果
boolean success = semaphore.tryAcquire();

// 尝试获取许可,最多等待指定时间
boolean success = semaphore.tryAcquire(1, TimeUnit.SECONDS);

// 获取所有可用的许可,并返回数量
int available = semaphore.drainPermits();

2. 释放许可的方法

java 复制代码
// 释放一个许可
semaphore.release();

// 释放指定数量的许可
semaphore.release(2);

3. 查询方法

java

复制代码
// 获取可用许可数
int available = semaphore.availablePermits();

// 查询是否有线程在等待许可
boolean hasQueuedThreads = semaphore.hasQueuedThreads();

// 获取等待许可的线程数
int queueLength = semaphore.getQueueLength();

四、Semaphore 原理

1. 内部结构

java

复制代码
public class Semaphore implements java.io.Serializable {
    private final Sync sync;
    
    abstract static class Sync extends AbstractQueuedSynchronizer {
        Sync(int permits) {
            setState(permits); // 将许可数存入AQS的state
        }
        
        // 获取许可(减少state值)
        protected int tryAcquireShared(int acquires) {
            // ...
        }
        
        // 释放许可(增加state值)
        protected boolean tryReleaseShared(int releases) {
            // ...
        }
    }
}

2. 实现原理(基于AQS)

Semaphore 底层使用 AQS(AbstractQueuedSynchronizer)实现:

共享锁模式
  • 将许可数存储在 AQS 的 state 变量中

  • acquire() 减少 state 值

  • release() 增加 state 值

java

复制代码
// 简化的获取许可逻辑
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

// AQS中的实现
public final void acquireSharedInterruptibly(int arg) {
    if (Thread.interrupted()) throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)  // 尝试获取许可
        doAcquireSharedInterruptibly(arg);  // 获取失败,加入队列等待
}

// 非公平锁的实现
protected int tryAcquireShared(int acquires) {
    for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 || compareAndSetState(available, remaining))
            return remaining;  // 返回剩余许可数
    }
}

五、使用场景示例

场景1:限流控制(连接池)

java

复制代码
public class ConnectionPool {
    private final Semaphore semaphore;
    private final List<Connection> pool = new ArrayList<>();
    
    public ConnectionPool(int poolSize) {
        this.semaphore = new Semaphore(poolSize);
        for (int i = 0; i < poolSize; i++) {
            pool.add(new Connection("Connection-" + i));
        }
    }
    
    public Connection getConnection() throws InterruptedException {
        semaphore.acquire();  // 获取许可
        return getAvailableConnection();
    }
    
    public void releaseConnection(Connection conn) {
        returnConnection(conn);
        semaphore.release();  // 释放许可
    }
    
    private synchronized Connection getAvailableConnection() {
        // 返回一个可用连接
        return pool.remove(0);
    }
    
    private synchronized void returnConnection(Connection conn) {
        pool.add(conn);
    }
}

场景2:多任务并行限制

java

复制代码
public class TaskExecutor {
    private final Semaphore semaphore = new Semaphore(5);  // 最多5个并行任务
    
    public void executeTask(Runnable task) {
        new Thread(() -> {
            try {
                semaphore.acquire();
                task.run();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }).start();
    }
}

场景3:生产者-消费者模式

java

复制代码
public class ProducerConsumer {
    private final Semaphore producerSemaphore = new Semaphore(3);  // 最多3个生产者
    private final Semaphore consumerSemaphore = new Semaphore(0);  // 初始没有产品
    private final Queue<String> queue = new LinkedList<>();
    
    public void produce(String item) throws InterruptedException {
        producerSemaphore.acquire();  // 获取生产许可
        synchronized (queue) {
            queue.offer(item);
            System.out.println("生产: " + item);
        }
        consumerSemaphore.release();  // 释放消费许可
    }
    
    public void consume() throws InterruptedException {
        consumerSemaphore.acquire();  // 获取消费许可
        String item;
        synchronized (queue) {
            item = queue.poll();
            System.out.println("消费: " + item);
        }
        producerSemaphore.release();  // 释放生产许可
    }
}

六、公平模式 vs 非公平模式

1. 非公平模式(默认)

java

复制代码
Semaphore semaphore = new Semaphore(1, false);  // 非公平
  • 特点:新来的线程可能比等待队列中的线程先获取到许可

  • 优点:吞吐量高

  • 缺点:可能产生线程饥饿

2. 公平模式

java

复制代码
Semaphore semaphore = new Semaphore(1, true);  // 公平
  • 特点:严格按照等待队列的顺序获取许可

  • 优点:公平,防止线程饥饿

  • 缺点:性能稍差

七、注意事项

1. 许可数可以超过初始值

java

复制代码
Semaphore semaphore = new Semaphore(2);
semaphore.release();  // 现在有3个许可
semaphore.release(2); // 现在有5个许可

2. 可以一次性获取/释放多个许可

java

复制代码
// 一次需要多个资源
semaphore.acquire(3);  // 需要3个许可
// 使用资源...
semaphore.release(3);  // 释放3个许可

3. 异常处理

java

复制代码
try {
    semaphore.acquire();
    // 业务逻辑
} catch (InterruptedException e) {
    // 处理中断
    Thread.currentThread().interrupt();  // 恢复中断状态
} finally {
    if (!Thread.currentThread().isInterrupted()) {
        semaphore.release();
    }
}

八、与其它同步工具对比

工具 特点 适用场景
Semaphore 控制并发访问数量 限流、资源池
CountDownLatch 一次性等待 等待多个任务完成
CyclicBarrier 可重复使用,所有线程相互等待 分批处理任务
ReentrantLock 独占锁 互斥访问

九、最佳实践

  1. 合理设置许可数:根据系统资源和性能要求设置

  2. 使用 try-finally 确保释放许可

  3. 考虑使用公平模式:如果对公平性有要求

  4. 监控队列长度:避免线程长时间等待

  5. 考虑使用 tryAcquire 带超时:避免死锁

总结

Semaphore 是一个强大的并发控制工具,它通过维护许可数量来控制对共享资源的访问。理解其基于 AQS 的实现原理有助于更好地使用它。在实际开发中,Semaphore 常用于限流、连接池管理、生产者-消费者模式等场景。

相关推荐
水水不水啊1 小时前
通过一个域名,借助IPV6免费远程访问自己家里的设备
前端·python·算法
nju_spy1 小时前
力扣每日一题(11.10-11.29)0-1 和 k 整除系列
python·算法·leetcode·前缀和·单调栈·最大公约数·0-1背包
名扬9111 小时前
webrtc编译问题-ubuntu
开发语言·python
无风之翼1 小时前
android12下拉菜单栏界面上方显示无内容
android·java
程序员梁白开1 小时前
从源码到实战:线程池处理任务的完整流程解析
java·jvm·spring·java-ee
u***1371 小时前
Tomcat的升级
java·tomcat
t***p9351 小时前
springboot项目读取 resources 目录下的文件的9种方式
java·spring boot·后端
岁月宁静1 小时前
从 JavaScript 到 Python:前端工程师的完全转换指南
前端·javascript·python
白云千载尽2 小时前
Python 初学者 / 中级开发者常踩坑的 10 个坑 —— 要用好几年才能彻底搞清楚的
开发语言·python