1. 写在前面
Semaphore 是一个用于控制多个线程对共享资源访问的同步工具。在正式阅读源码前,我先抛出几个问题看看大家有没有思考过:
- Semaphore 的基本用法是什么?
- 如何创建一个 Semaphore?
- Semaphore 的公平性是什么?
- Semaphore 适用于哪些场景?
- 如何使用 Semaphore 实现一个简单的限流器?
- emaphore 的内部实现原理是什么?
- Semaphore 如何保证线程安全?
- 如何使用 Semaphore 控制对共享资源的访问。
- 如何使用 Semaphore 实现生产者-消费者模式?
- Semaphore 与 ReentrantLock 的区别是什么?
- 如何使用 Semaphore 实现一个读写锁?
2. 从使用说起
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final int MAX_CONCURRENT_THREADS = 3;
private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_THREADS);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Worker()).start();
}
}
static class Worker implements Runnable {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " acquired a permit.");
// 模拟对共享资源的访问
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println(Thread.currentThread().getName() + " released a permit.");
semaphore.release();
}
}
}
}
这段代码演示了如何使用 Semaphore 来控制对共享资源的并发访问。它实现了一个简单的限流机制,确保最多只有三个线程可以同时访问共享资源。
2.1 类定义和成员变量
public class SemaphoreExample {
private static final int MAX_CONCURRENT_THREADS = 3;
private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_THREADS);
- 定义一个名为 SemaphoreExample 的公共类
- 定义一个常量 MAX_CONCURRENT_THREADS,表示最多允许同时访问共享资源的线程数,这里设置为3
- 创建一个 Semaphore 实例 semaphore,初始许可数量为 MAX_CONCURRENT_THREADS
2.2 主方法
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Worker()).start();
}
}
- main 方法是程序的入口。
- 使用一个 for 循环创建并启动10个线程,每个线程执行 Worker 类的 run 方法。
2.3 Worker 类
static class Worker implements Runnable {
@Override
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " acquired a permit.");
// 模拟对共享资源的访问
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println(Thread.currentThread().getName() + " released a permit.");
semaphore.release();
}
}
}
}
- 定义一个静态内部类 Worker,实现 Runnable 接口。
- 在 run 方法中:
- semaphore.acquire():尝试获取一个许可。如果没有可用的许可,线程会被阻塞,直到有许可可用。
- 打印当前线程名称和消息,表示线程已获取许可。
- 使用 Thread.sleep(2000) 模拟对共享资源的访问,休眠2秒。
- 在 finally 块中:
- 打印当前线程名称和消息,表示线程释放了许可。
- semaphore.release():释放一个许可,增加可用许可的数量。
2.4 代码执行流程
- 程序启动后,main 方法创建并启动10个线程,每个线程执行 Worker 类的 run 方法。
- 最多只有3个线程可以同时获取许可并执行对共享资源的访问,其余线程会被阻塞,直到有线程释放许可。
- 每个线程获取许可后,打印消息并休眠2秒,模拟对共享资源的访问。
- 休眠结束后,线程释放许可,并打印消息。
- 被阻塞的线程在有许可可用时继续执行。
3. Semaphore 的公平性是什么?
Semaphore 可以是公平的或非公平的。公平的 Semaphore 保证线程按照它们请求许可的顺序获得许可。可以通过构造函数的第二个参数来指定:
Semaphore semaphore = new Semaphore(3, true); // 公平的 Semaphore
4. Semaphore 适用于哪些场景?
- 控制对有限资源的访问,例如数据库连接池、文件访问等。
- 实现简单的限流机制。
- 作为一种信号量机制,用于线程间通信。
5. 如何使用 Semaphore 实现一个简单的限流器?
通过限制许可数量来控制并发线程数:
Semaphore semaphore = new Semaphore(5); // 允许最多5个线程同时访问
public void accessResource() {
try {
semaphore.acquire();
// 访问资源的代码
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}
6. Semaphore 的内部实现原理是什么?
Semaphore 使用一个计数器来跟踪可用的许可数量。acquire() 方法会尝试减少计数器,如果计数器为零,线程会被阻塞。release() 方法会增加计数器,并唤醒等待的线程。
7. Semaphore 如何保证线程安全?
Semaphore 使用内部的 AbstractQueuedSynchronizer (AQS) 来管理许可计数和线程的阻塞/唤醒操作,确保线程安全。
8. 如何使用 Semaphore 实现生产者-消费者模式?
Semaphore 可以用于控制生产者和消费者之间的同步,例如:
import java.util.concurrent.Semaphore;
public class ProducerConsumerExample {
private static final int BUFFER_SIZE = 10;
private static final Semaphore emptySlots = new Semaphore(BUFFER_SIZE);
private static final Semaphore fullSlots = new Semaphore(0);
private static final Semaphore mutex = new Semaphore(1);
private static final int[] buffer = new int[BUFFER_SIZE];
private static int in = 0, out = 0;
public static void main(String[] args) {
Thread producer = new Thread(new Producer());
Thread consumer = new Thread(new Consumer());
producer.start();
consumer.start();
}
static class Producer implements Runnable {
@Override
public void run() {
try {
while (true) {
emptySlots.acquire();
mutex.acquire();
buffer[in] = 1; // 生产一个产品
in = (in + 1) % BUFFER_SIZE;
mutex.release();
fullSlots.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
static class Consumer implements Runnable {
@Override
public void run() {
try {
while (true) {
fullSlots.acquire();
mutex.acquire();
int item = buffer[out]; // 消费一个产品
out = (out + 1) % BUFFER_SIZE;
mutex.release();
emptySlots.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
9. Semaphore 与 ReentrantLock 的区别是什么?
- Semaphore 控制的是许可数量,可以允许多个线程同时访问共享资源;ReentrantLock 是一个互斥锁,只允许一个线程访问共享资源。
- Semaphore 可以用来实现限流和信号量机制,而 ReentrantLock 主要用于实现互斥访问。
10. 如何使用 Semaphore 实现一个读写锁?
读写锁可以通过两个 Semaphore 来实现,一个控制读操作,一个控制写操作。
import java.util.concurrent.Semaphore;
public class ReadWriteLock {
private final Semaphore readLock = new Semaphore(1); // 控制读操作的信号量
private final Semaphore writeLock = new Semaphore(1); // 控制写操作的信号量
private int readCount = 0; // 记录当前有多少个读线程
public void acquireReadLock() throws InterruptedException {
readLock.acquire(); // 获取读锁
synchronized (this) {
readCount++;
if (readCount == 1) {
writeLock.acquire(); // 第一个读线程获取写锁,阻止写操作
}
}
readLock.release(); // 释放读锁
}
public void releaseReadLock() {
synchronized (this) {
readCount--;
if (readCount == 0) {
writeLock.release(); // 最后一个读线程释放写锁,允许写操作
}
}
}
public void acquireWriteLock() throws InterruptedException {
writeLock.acquire(); // 获取写锁
}
public void releaseWriteLock() {
writeLock.release(); // 释放写锁
}
public static void main(String[] args) {
ReadWriteLock rwLock = new ReadWriteLock();
// 创建并启动读线程
for (int i = 0; i < 5; i++) {
new Thread(new Reader(rwLock)).start();
}
// 创建并启动写线程
for (int i = 0; i < 2; i++) {
new Thread(new Writer(rwLock)).start();
}
}
static class Reader implements Runnable {
private final ReadWriteLock rwLock;
public Reader(ReadWriteLock rwLock) {
this.rwLock = rwLock;
}
@Override
public void run() {
try {
rwLock.acquireReadLock();
System.out.println(Thread.currentThread().getName() + " acquired read lock.");
// 模拟读操作
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println(Thread.currentThread().getName() + " released read lock.");
rwLock.releaseReadLock();
}
}
}
static class Writer implements Runnable {
private final ReadWriteLock rwLock;
public Writer(ReadWriteLock rwLock) {
this.rwLock = rwLock;
}
@Override
public void run() {
try {
rwLock.acquireWriteLock();
System.out.println(Thread.currentThread().getName() + " acquired write lock.");
// 模拟写操作
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println(Thread.currentThread().getName() + " released write lock.");
rwLock.releaseWriteLock();
}
}
}
}
10.1 ReadWriteLock 类
- readLock:用于控制读操作的信号量,初始许可数量为1。
- writeLock:用于控制写操作的信号量,初始许可数量为1。
- readCount:记录当前有多少个读线程在访问共享资源。
10.2 acquireReadLock() 方法
- 获取读锁 readLock。
- 使用 synchronized 块保护对 readCount 的访问。如果这是第一个读线程,获取写锁 writeLock,阻止写操作。
- 释放读锁 readLock。
10.3 releaseReadLock() 方法
使用 synchronized 块保护对 readCount 的访问。如果这是最后一个读线程,释放写锁 writeLock,允许写操作
10.4 acquireWriteLock() 方法
获取写锁 writeLock
10.5 releaseWriteLock() 方法
释放写锁 writeLock
10.6 Reader 和 Writer 类
- Reader 类实现读操作,获取和释放读锁
- Writer 类实现写操作,获取和释放写锁
系列文章
7.jdk源码阅读之ConcurrentHashMap(上)