在 Java 并发编程中,Semaphore
是一个非常有用的同步工具类,属于 java.util.concurrent
包的一部分。它可以用来控制对某一资源的访问数量,允许多个线程同时访问一定数量的资源。本文将详细介绍 Semaphore
的概念、使用技巧和示例。
1. 什么是 Semaphore?
Semaphore
(信号量)是一种用于限制线程并发数量的机制。它维护一个可用的许可数量,每个线程在进入临界区前必须先获得许可,离开时释放许可。信号量可以用来实现各种并发控制,如互斥锁、限流器、生产者-消费者模型等。
Semaphore
主要有两个构造方法:
Semaphore(int permits)
:创建一个具有指定许可数量的信号量。Semaphore(int permits, boolean fair)
:指定是否为公平模式的信号量。如果为true
,则按照先来先得的顺序分配许可。
2. Semaphore 的核心方法
Semaphore
主要提供以下几个方法来管理许可:
acquire()
:获取一个许可,如果没有可用的许可,当前线程会被阻塞,直到有可用的许可。acquire(int permits)
:获取指定数量的许可。tryAcquire()
:尝试获取一个许可,如果成功则立即返回true
,否则返回false
。tryAcquire(int permits)
:尝试获取指定数量的许可。release()
:释放一个许可。release(int permits)
:释放指定数量的许可。
3. Semaphore 的使用技巧
Semaphore
可以用于解决以下几种典型的并发控制问题:
1. 控制访问数量
Semaphore
可以限制同时访问某一资源的线程数量。常见场景如:数据库连接池、限流、实现限速等。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final int THREAD_COUNT = 30;
private static final Semaphore semaphore = new Semaphore(10); // 最多允许 10 个线程同时访问
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.execute(() -> {
try {
semaphore.acquire(); // 获取一个许可
System.out.println(Thread.currentThread().getName() + " is accessing...");
Thread.sleep(1000); // 模拟访问资源
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放一个许可
}
});
}
executorService.shutdown();
}
}
在上述示例中,semaphore
限制了最多有 10 个线程可以同时访问资源。其他线程必须等待,直到有可用的许可。
2. 实现生产者-消费者模式
Semaphore
可以用来实现生产者-消费者模型,其中一个信号量控制生产者生产的产品数量,另一个信号量控制消费者的消费数量。
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Semaphore;
public class ProducerConsumerExample {
private static final int BUFFER_SIZE = 5;
private static final Semaphore items = new Semaphore(0); // 表示当前有多少个产品可消费
private static final Semaphore spaces = new Semaphore(BUFFER_SIZE); // 表示缓冲区有多少个空间可放入产品
private static final Queue<Integer> buffer = new LinkedList<>();
public static void main(String[] args) {
Thread producer = new Thread(() -> {
try {
int value = 0;
while (true) {
spaces.acquire(); // 等待缓冲区有空位
synchronized (buffer) {
buffer.add(value);
System.out.println("Produced: " + value);
}
items.release(); // 增加一个可消费的产品数量
value++;
Thread.sleep(500); // 模拟生产时间
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
while (true) {
items.acquire(); // 等待有产品可消费
int value;
synchronized (buffer) {
value = buffer.poll();
System.out.println("Consumed: " + value);
}
spaces.release(); // 增加一个缓冲区空间
Thread.sleep(1000); // 模拟消费时间
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
在这个例子中,items
信号量表示可以消费的产品数量,而 spaces
信号量表示缓冲区中可用的空间。生产者生产一个产品后,items
许可数增加,spaces
减少;消费者消费一个产品后,items
减少,spaces
增加。
3. 实现互斥锁
Semaphore
的初始许可数为 1 时,可以用来实现互斥锁(类似于 ReentrantLock
或 synchronized
)。
import java.util.concurrent.Semaphore;
public class MutexExample {
private static final Semaphore mutex = new Semaphore(1); // 创建一个信号量作为互斥锁
public static void main(String[] args) {
Thread t1 = new Thread(new Task(), "Thread-1");
Thread t2 = new Thread(new Task(), "Thread-2");
t1.start();
t2.start();
}
static class Task implements Runnable {
@Override
public void run() {
try {
mutex.acquire(); // 获取许可,相当于锁定
System.out.println(Thread.currentThread().getName() + " is in critical section.");
Thread.sleep(2000); // 模拟临界区操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " is leaving critical section.");
mutex.release(); // 释放许可,相当于解锁
}
}
}
}
此示例使用信号量 mutex
实现了一个简单的互斥锁效果,确保同一时间只有一个线程能进入临界区。
4. 总结
Semaphore
是一种非常灵活的并发控制工具,可以控制对资源的访问数量。Semaphore
常用于实现限流、互斥锁、生产者-消费者模型等并发场景。- 使用
Semaphore
可以显式地控制线程的同步行为,但需要小心避免死锁和资源泄露问题。
通过理解 Semaphore
的用法和应用场景,可以编写出更高效和健壮的多线程 Java 程序。