Semaphore(信号量)是Java并发包(java.util.concurrent
)中一个重要的同步工具类,用于控制对共享资源的并发访问数量。下面我将从多个维度全面解析Semaphore的实现原理和使用方法。
Semaphore基于经典的计数器信号量模型,主要包含两个原子操作:
P操作(acquire):申请资源,计数器减1
V操作(release):释放资源,计数器加1
基于AQS(AbstractQueuedSynchronizer)实现
支持公平和非公平两种模式
许可证数量可以动态调整
提供可中断和超时获取能力
Semaphore的内部类Sync继承自AQS:
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits); // 使用AQS的state存储许可证数量
}
// 其他实现...
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors()) // 检查是否有前驱节点
return -1;
// 剩余逻辑与非公平模式相同...
}
}
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1); // 委托给AQS实现
}
// AQS中的实现
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg); // 加入等待队列
}
public void release() {
sync.releaseShared(1);
}
// AQS中的实现
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared(); // 唤醒后续节点
return true;
}
return false;
}
public class ObjectPool {
private final Semaphore semaphore;
private final BlockingQueue pool;
public ObjectPool(int size, Supplier supplier) {
this.semaphore = new Semaphore(size);
this.pool = new LinkedBlockingQueue<>();
for (int i = 0; i < size; i++) {
pool.offer(supplier.get());
}
}
public T borrow() throws InterruptedException {
semaphore.acquire();
return pool.take();
}
public void release(T obj) {
pool.offer(obj);
semaphore.release();
}
}
public class RateLimiter {
private final Semaphore semaphore;
private final long period;
private final TimeUnit unit;
public RateLimiter(int permits, long period, TimeUnit unit) {
this.semaphore = new Semaphore(permits);
this.period = period;
this.unit = unit;
startResetTask();
}
private void startResetTask() {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
semaphore.drainPermits();
semaphore.release(semaphore.availablePermits());
}, period, period, unit);
}
public void execute(Runnable task) throws InterruptedException {
semaphore.acquire();
try {
task.run();
} finally {
semaphore.release();
}
}
}
合理设置许可证数量:
过多会导致资源浪费
过少会导致线程频繁阻塞
选择适当的公平性:
高竞争环境:非公平模式(默认)吞吐量更高
需要严格顺序:公平模式
避免长时间持有许可证:
// 反例 - 在临界区内执行耗时操作
semaphore.acquire();
try {
doTimeConsumingWork(); // 阻塞其他线程
} finally {
semaphore.release();
}
4.考虑使用tryAcquire:
if (semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)) {
try {
// 快速操作
} finally {
semaphore.release();
}
} else {
// 备用方案
}
特性 | Semaphore | CountDownLatch | CyclicBarrier | ReentrantLock |
---|---|---|---|---|
主要用途 | 资源访问控制 | 一次性等待 | 多线程集合点 | 互斥访问 |
可重用 | 是 | 否 | 是 | 是 |
许可证数量 | 可配置 | 固定1 | N/A | 固定1 |
释放机制 | 任意线程可释放 | 计数减到0 | 所有线程到达 | 必须持有者释放 |
条件支持 | 否 | 否 | 否 | 是 |
许可证泄漏:
现象:可用许可证逐渐减少
解决:确保每次acquire都有对应的release
线程饥饿:
现象:某些线程长期无法获取资源
解决:改用公平模式或调整许可证数量
死锁场景:
// 线程1
semaphoreA.acquire();
semaphoreB.acquire(); // 可能阻塞
// 线程2
semaphoreB.acquire();
semaphoreA.acquire(); // 可能阻塞
Semaphore是Java并发编程中非常强大的工具,合理使用可以有效解决资源控制、流量限制等并发问题。理解其底层实现机制有助于更准确地应用于各种复杂场景。