概述

Semaphore(信号量)是另一种同步机制,它管理一组许可证。每个 acquire() 调用会获取一个许可证,如果没有可用的许可证,则阻塞直到有许可证可用;而每次调用 release() 会释放一个许可证,增加可用的许可证数量。因此,Semaphore 可以用来限制同时访问某些资源的线程数目,确保不会超过设定的最大并发数10。

信号量分为公平性和非公平性两种模式。在公平模式下,信号量会按照请求的顺序分配许可证给等待的线程;而非公平模式则可能跳过前面等待的线程,优先响应新的请求。Semaphore 的典型应用场景包括但不限于控制对有限资源池(如数据库连接)的访问、实现生产者-消费者问题等。

信号量的工作原理

信号量通过维护一个内部的许可集合来工作。当创建 Semaphore 对象时,需要指定初始的许可数量。每个线程在访问受保护的资源之前必须先从信号量中获取一个或多个许可(通过调用 acquire() 方法)。如果此时还有足够的许可可用,则该线程可以获得这些许可并继续执行;否则,线程将被阻塞直到有其他线程释放了相应的许可(通过调用 release() 方法)。一旦线程完成了对资源的操作,就应该立即释放所持有的许可,以便其他等待的线程可以继续。

公平与非公平模式

Semaphore 支持两种不同的访问策略:公平和非公平。在公平模式下,信号量会按照线程请求许可的时间顺序来分配许可,确保较早请求的线程优先获得许可。而在非公平模式中,可能会出现“抢占”的情况,即后来的线程可能比前面等待的线程更早地获取到许可。默认情况下,Semaphore 是非公平的,但可以在构造函数中指定是否启用公平模式

构造函数

Semaphore 提供了两个主要的构造函数:构造函数

  • Semaphore(int permits):创建一个新的 Semaphore 实例,并初始化为给定数量的许可。
  • Semaphore(int permits, boolean fair):除了指定许可的数量外,还可以选择是否采用公平策略8。

常见方法

  • void acquire() 和 void acquire(int permits):分别尝试获取一个或多个许可,如果没有足够的许可则会阻塞当前线程,直到有足够的许可为止。
  • boolean tryAcquire() 和 boolean tryAcquire(int permits):尝试立即获取一个或多个许可,如果成功则返回 true,否则返回 false 而不会阻塞。
  • boolean tryAcquire(long timeout, TimeUnit unit) 和 boolean tryAcquire(int permits, long timeout, TimeUnit unit):尝试在指定时间内获取一个或多个许可,超时未获取到则返回 false。
  • void release() 和 void release(int permits):释放一个或多个许可,增加信号量中可用的许可数量。

应用场景

Semaphore 的典型应用场景包括但不限于:

  • 限流:例如,在 Web 服务器中限制同时处理的最大请求数量,防止服务器过载。
  • 资源池管理:如数据库连接池、文件句柄等有限资源的管理,确保任何时候都不会超过设定的最大并发使用量。
  • 互斥锁:当信号量只允许一个线程进入临界区段时,它可以作为互斥锁使用。

示例代码

下面是一个简单的例子,展示了如何使用 Semaphore 来限制同时访问某资源的线程数量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.concurrent.Semaphore;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SemaphoreExample {
private static final int MAX_THREADS = 2; // 最大并发线程数
private static final Semaphore semaphore = new Semaphore(MAX_THREADS, true); // 创建一个公平的信号量

public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(10);

for (int i = 0; i < 10; i++) {
executor.submit(() -> {
try {
semaphore.acquire(); // 获取许可
System.out.println(Thread.currentThread().getName() + " is working.");
Thread.sleep((long)(Math.random() * 5000)); // 模拟工作时间
System.out.println(Thread.currentThread().getName() + " has finished.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
});
}

executor.shutdown();
executor.awaitTermination(1, java.util.concurrent.TimeUnit.HOURS);
}
}

在这个例子中,我们创建了一个最大并发线程数为 2 的 Semaphore,并且使用了公平模式。这意味着如果有多个线程竞争许可,它们将以 FIFO(先进先出)的方式依次获得许可。通过这种方式,我们可以有效地控制同时访问共享资源的线程数量,从而避免潜在的竞争条件或资源耗尽问题23。

总之,Semaphore 是一种强大的同步机制,适用于需要限制并发访问数量的各种场景。正确配置和使用它可以显著提高系统的稳定性和性能。