什么是信号量(Semaphore)?
信号量是一种用于控制并发访问资源的工具。它可以看作是一个计数器,表示某个资源当前还有多少个“许可证(permit)”可以被线程使用。线程想要访问资源时,先“拿”一个许可证;用完资源后,再“归还”许可证。如果没有可用的许可证,线程就会被阻塞,直到有许可证可用。
可以把它想象成一个公共停车场:
- 停车场有固定数量的停车位(许可证)。
- 当车(线程)进入时,需要占用一个停车位(拿一个许可证)。
- 如果停车位满了(许可证用完了),车就得在外面排队(线程阻塞)。
- 当有车离开时,会释放一个停车位(归还许可证),这样后面的车才能进入。
Java 中的 Semaphore
在 Java 中,Semaphore
是 java.util.concurrent
包下的一个类,用来实现上述信号量功能。它有两种模式:
- 公平模式:按照线程请求许可证的顺序(FIFO)来分配许可证。
- 非公平模式(默认):线程可能会“抢占”许可证,优先级不一定按顺序。
核心方法
acquire()
:尝试获取一个许可证。如果没有可用的许可证,线程会阻塞,直到有许可证可用。release()
:释放一个许可证,把它还回信号量。tryAcquire()
:尝试立即获取一个许可证,如果没有可用的许可证,不会阻塞,而是直接返回false
。- 构造函数:
Semaphore(int permits)
:创建一个指定初始许可证数量的信号量。Semaphore(int permits, boolean fair)
:在指定许可证数量的基础上,决定是否使用公平模式。
信号量的使用场景
-
限制资源访问
如果有多个线程需要访问有限的资源(比如数据库连接池或线程池中的线程),可以用信号量来限制同时访问的线程数量。 -
实现流量控制
在高并发场景中,可以用信号量限制每秒处理的请求数量,防止系统过载。 -
控制多线程任务的执行顺序
通过信号量,可以让某些线程等待其他线程完成某个任务后再继续执行。
Semaphore
的实现细节
1. 许可证的管理
Semaphore
的核心是一个计数器(permits
),表示当前可用的许可证数量。这个计数器的初始值由构造函数指定。
- 每次调用
acquire()
时,计数器会减 1。 - 每次调用
release()
时,计数器会加 1。
计数器的值可能为:
- 正数:表示还有足够的许可证可用。
- 零:表示许可证用完了,线程需要等待。
- 负数:不会出现,因为线程会阻塞直到许可证可用。
2. 底层依赖 AQS
Semaphore
的底层实现依赖于 AbstractQueuedSynchronizer(AQS),这是 Java 并发包中一个非常重要的工具类,用来管理线程的同步状态和阻塞队列。
- 状态变量:AQS 的状态变量用来表示当前的许可证数量。
- 线程等待队列:当许可证不足时,线程会被放入 AQS 的等待队列中,直到有线程调用
release()
释放许可证。
3. 公平与非公平模式
- 非公平模式(默认):线程可能会“抢占”许可证,即使有其他线程已经在等待队列中排队。这样可以提高整体吞吐量。
- 公平模式:线程会严格按照请求的顺序获取许可证,先到先得。这种模式的性能可能会稍微差一些,因为需要更多的线程调度。
4. 阻塞与唤醒
- 当线程调用
acquire()
时,如果没有可用许可证,线程会进入等待状态(阻塞)。 - 当另一个线程调用
release()
时,信号量会唤醒等待队列中的一个线程,让它重新尝试获取许可证。
5. 可重入性
Semaphore
本身不是可重入的。也就是说,如果一个线程多次调用 acquire()
,它需要多次调用 release()
才能释放所有的许可证。
示例场景:停车场模型
假设有一个停车场,最多只能容纳 3 辆车(许可证数量为 3)。多个线程模拟车辆尝试进入停车场。
- 当停车位有空余时,车辆直接进入。
- 如果停车位满了,车辆必须等待。
- 当有车辆离开时,停车位变空,等待的车辆可以进入。
使用 Semaphore
,可以轻松实现这种逻辑。
总结
-
Semaphore
是什么?
它是一种计数器,用来限制同时访问某个资源的线程数量。 -
如何工作?
- 线程调用
acquire()
获取许可证,计数器减 1。如果没有可用的许可证,线程会阻塞。 - 线程调用
release()
归还许可证,计数器加 1,唤醒等待的线程。
- 线程调用
-
适用场景
- 限制资源的并发访问(如数据库连接池)。
- 流量控制(如限制同时处理的请求数)。
- 控制线程执行顺序。
-
底层实现
- 基于 AQS(AbstractQueuedSynchronizer)。
- 许可证数量通过 AQS 的状态变量管理。
- 公平与非公平模式决定许可证分配的策略。
