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 | 独占锁 | 互斥访问 |
九、最佳实践
-
合理设置许可数:根据系统资源和性能要求设置
-
使用 try-finally 确保释放许可
-
考虑使用公平模式:如果对公平性有要求
-
监控队列长度:避免线程长时间等待
-
考虑使用 tryAcquire 带超时:避免死锁
总结
Semaphore 是一个强大的并发控制工具,它通过维护许可数量来控制对共享资源的访问。理解其基于 AQS 的实现原理有助于更好地使用它。在实际开发中,Semaphore 常用于限流、连接池管理、生产者-消费者模式等场景。