Java Semaphore 深入解析
简介
在 Java 并发编程中,Semaphore(信号量)是一个强大的工具,用于控制对有限资源的访问。它可以用来限制同时访问特定资源的线程数量,从而避免资源过度使用或竞争。本文将详细介绍 Java Semaphore 的基础概念、使用方法、常见实践以及最佳实践,帮助读者深入理解并高效使用这一并发工具。
目录
- 基础概念
- 使用方法
- 常见实践
- 最佳实践
- 小结
- 参考资料
基础概念
Semaphore 是一个计数信号量,它维护了一个许可(permit)的计数。线程在访问共享资源之前,必须先从 Semaphore 中获取一个许可,如果许可数量为 0,则线程会被阻塞,直到有其他线程释放许可。当线程使用完资源后,需要将许可归还给 Semaphore,以便其他线程可以继续使用。
Semaphore 有两种模式:公平模式和非公平模式。公平模式下,线程会按照请求许可的顺序依次获取许可;非公平模式下,线程获取许可的顺序是不确定的,可能会有线程插队获取许可。
使用方法
构造函数
Semaphore 有两个主要的构造函数:
- Semaphore(int permits)
:创建一个具有指定许可数量的非公平 Semaphore。
- Semaphore(int permits, boolean fair)
:创建一个具有指定许可数量的 Semaphore,并指定是否使用公平模式。
主要方法
acquire()
:获取一个许可,如果没有可用许可,则线程会被阻塞。acquire(int permits)
:获取指定数量的许可,如果没有足够的许可,则线程会被阻塞。release()
:释放一个许可。release(int permits)
:释放指定数量的许可。tryAcquire()
:尝试获取一个许可,如果有可用许可,则立即返回true
,否则返回false
。tryAcquire(int permits)
:尝试获取指定数量的许可,如果有足够的许可,则立即返回true
,否则返回false
。tryAcquire(long timeout, TimeUnit unit)
:尝试在指定的时间内获取一个许可,如果在时间内有可用许可,则返回true
,否则返回false
。tryAcquire(int permits, long timeout, TimeUnit unit)
:尝试在指定的时间内获取指定数量的许可,如果在时间内有足够的许可,则返回true
,否则返回false
。
代码示例
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final int MAX_THREADS = 3;
private static Semaphore semaphore = new Semaphore(MAX_THREADS);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Worker());
thread.start();
}
}
static class Worker implements Runnable {
@Override
public void run() {
try {
// 获取许可
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获得许可,开始工作");
// 模拟工作
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " 工作完成,释放许可");
// 释放许可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在上述代码中,我们创建了一个 Semaphore,初始许可数量为 3。然后创建了 10 个线程,每个线程在执行工作前需要先获取许可,工作完成后释放许可。由于许可数量有限,最多只有 3 个线程可以同时工作。
常见实践
限制资源访问
Semaphore 最常见的用途是限制对有限资源的访问。例如,在一个数据库连接池中,我们可以使用 Semaphore 来限制同时使用的数据库连接数量,避免过多的连接导致数据库性能下降。
import java.util.concurrent.Semaphore;
public class DatabaseConnectionPool {
private static final int MAX_CONNECTIONS = 5;
private Semaphore semaphore = new Semaphore(MAX_CONNECTIONS);
public void getConnection() {
try {
// 获取许可
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获得数据库连接");
// 模拟使用连接
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " 使用完数据库连接,释放连接");
// 释放许可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
DatabaseConnectionPool pool = new DatabaseConnectionPool();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> pool.getConnection());
thread.start();
}
}
}
实现生产者 - 消费者模型
Semaphore 也可以用于实现生产者 - 消费者模型。我们可以使用两个 Semaphore 来控制生产者和消费者的行为,一个 Semaphore 用于控制缓冲区的空闲位置,另一个 Semaphore 用于控制缓冲区中的可用数据。
import java.util.concurrent.Semaphore;
public class ProducerConsumerExample {
private static final int BUFFER_SIZE = 5;
private static int[] buffer = new int[BUFFER_SIZE];
private static int in = 0;
private static int out = 0;
private static Semaphore empty = new Semaphore(BUFFER_SIZE);
private static Semaphore full = new Semaphore(0);
static class Producer implements Runnable {
@Override
public void run() {
try {
while (true) {
// 等待缓冲区有空位
empty.acquire();
// 生产数据
buffer[in] = (int) (Math.random() * 100);
System.out.println(Thread.currentThread().getName() + " 生产数据: " + buffer[in]);
in = (in + 1) % BUFFER_SIZE;
// 通知消费者有新数据
full.release();
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Consumer implements Runnable {
@Override
public void run() {
try {
while (true) {
// 等待缓冲区有数据
full.acquire();
// 消费数据
int data = buffer[out];
System.out.println(Thread.currentThread().getName() + " 消费数据: " + data);
out = (out + 1) % BUFFER_SIZE;
// 通知生产者有空闲位置
empty.release();
Thread.sleep(1500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread producerThread = new Thread(new Producer());
Thread consumerThread = new Thread(new Consumer());
producerThread.start();
consumerThread.start();
}
}
最佳实践
- 合理设置许可数量:根据实际需求合理设置 Semaphore 的初始许可数量,避免许可数量过多或过少导致资源浪费或性能下降。
- 确保许可的获取和释放:在使用 Semaphore 时,一定要确保每个线程在获取许可后都能正确释放许可,避免出现死锁或资源泄漏的问题。可以使用
try-finally
块来确保许可的释放。
try {
semaphore.acquire();
// 执行操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
- 选择合适的模式:根据实际情况选择公平模式或非公平模式。公平模式可以保证线程按照请求顺序获取许可,但可能会影响性能;非公平模式可以提高性能,但可能会导致某些线程长时间得不到许可。
小结
Java Semaphore 是一个强大的并发工具,用于控制对有限资源的访问。通过本文的介绍,我们了解了 Semaphore 的基础概念、使用方法、常见实践以及最佳实践。在实际开发中,合理使用 Semaphore 可以帮助我们更好地管理资源,提高程序的性能和稳定性。
参考资料
- 《Effective Java》