在并发编程中,Semaphore(信号量)是一种重要的同步辅助类,用于控制对共享资源的访问。它是java.util.concurrent(JUC)包提供的工具之一,在Java语言中被广泛应用。Semaphore的核心思想是维护一组许可证,它可以控制同时访问特定资源的线程数量。
Semaphore的工作原理
Semaphore管理一系列许可证(permits),线程可以通过 acquire()
方法来获取许可证,如果Semaphore持有足够的许可证,则允许线程的执行,如果没有,则线程需要等待,直到有线程释放许可证。线程执行完毕后,应调用 release()
方法来释放许可证,以供其他线程使用。
Semaphore的主要方法
acquire()
: 请求一个许可证。如果许可证不可用,当前线程将被阻塞,直到有许可证被释放。acquire(int permits)
: 请求给定数量的许可证。release()
: 释放一个许可证,将其返回给Semaphore。release(int permits)
: 释放多个许可证。tryAcquire()
: 尝试获取一个许可证,如果没有可用的许可证,立即返回false。tryAcquire(long timeout, TimeUnit unit)
: 尝试在给定时间内获取许可证,如果在指定的时间内没有获取到,返回false。
Semaphore的应用场景
Semaphore常被用于资源池(如数据库连接池、线程池)的管理,保证有限的资源被合理地分配。它也可以用来实现某些特殊的同步需求,比如限流,即限制某段时间内的访问量。
一个简单的Semaphore示例
下面举例说明如何使用Semaphore来控制对资源的访问:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
// 创建Semaphore实例并指定许可证数量为5
private static final Semaphore semaphore = new Semaphore(5);
public static void main(String[] args) {
// 模拟有10个线程同时请求访问资源
for (int i = 0; i < 10; i++) {
new Thread(new Worker(i)).start();
}
}
static class Worker implements Runnable {
private int num;
public Worker(int num) {
this.num = num;
}
@Override
public void run() {
try {
// 请求许可证
semaphore.acquire();
System.out.println("线程" + num + "获取许可证,当前可用许可证数量:" + semaphore.availablePermits());
// 模拟业务操作,线程睡眠2秒
Thread.sleep(2000);
// 释放许可证
semaphore.release();
System.out.println("线程" + num + "释放许可证,当前可用许可证数量:" + semaphore.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在这个示例中,我们定义了一个Semaphore,它持有5个许可证。然后,启动了10个线程要求访问共享资源。当一个线程开始运行时,它会尝试通过 acquire()
方法从Semaphore获取许可证,如果获取到许可证,线程将执行,并在完成后释放许可证。如果许可证全部被其他线程持有,后来的线程将被阻塞,直到有许可证释放。
注意事项和最佳实践
- 确保资源的使用量不会超过Semaphore允许的许可证数目。
- 经典的错误是忘记释放许可证,这将导致其他线程无法访问资源。
- 尽量使用
tryAcquire
带有超时的重载版本,避免永久等待资源的情况发生。 - 在使用Semaphore时,建议在
finally
代码块中释放许可证,确保许可证总能被释放。 - Semaphore不跟踪哪个线程获取了许可证,因此任何线程都可以释放许可证,这点在设计同步策略时需要注意。
信号量Semaphore是并发编程中的一种高级同步机制,它可以在复杂的资源共享场景中发挥重要作用。理解它的工作原理及正确的使用方法对实现高效且健壮的并发控制至关重要。