码到三十五 : 个人主页 心中有诗画,指尖舞代码,目光览世界,步履越千山,人间尽值得 !
在Java的并发编程中,为了协调多个线程对共享资源的访问,Java提供了多种同步工具。其中,
Semaphore
(信号量)是一个非常重要的同步辅助类,它允许多个线程同时访问一个或多个共享资源。本文将深入探讨Semaphore
的工作原理、特性以及适用场景。
Semaphore
,即信号量,是操作系统中的一个经典概念。在Java并发包(java.util.concurrent,简称JUC)中,Semaphore
类实现了这一概念,用于控制同时访问特定资源的线程数量。与synchronized
关键字和ReentrantLock
类不同,Semaphore
可以实现更为复杂的线程同步需求,允许一定数量的线程同时访问共享资源。
Semaphore
内部维护了一组许可证(permits)。每个线程在访问共享资源之前,必须先获取一个许可证。如果许可证不足,则线程将被阻塞,直到有许可证可用。当线程释放资源时,它会归还一个许可证,从而允许其他等待的线程获取资源。
通过控制许可证的数量,Semaphore
可以实现对共享资源访问的精细控制。例如,如果有一个需要限制并发访问次数的资源池,就可以使用Semaphore
来实现。
Semaphore
可以配置为公平的或非公平的。公平的Semaphore
将按照线程请求许可证的顺序来分配它们,而非公平的Semaphore
则不保证这种顺序。CountDownLatch
等一次性使用的同步工具不同,Semaphore
可以多次使用。一旦线程释放了许可证,其他线程就可以再次获取它。Semaphore
可以灵活地控制对资源池的并发访问。这对于保护有限资源或实现流量控制非常有用。首先,Semaphore
类中的主要组成部分是一个继承自AbstractQueuedSynchronizer
(AQS)的内部类Sync
。AbstractQueuedSynchronizer
是Java并发包中提供的一个用于构建锁和其他同步组件的基础框架。
public class Semaphore implements java.io.Serializable {
// 当前的许可数
private final Sync sync;
// 构造函数,创建一个具有给定许可数的Semaphore,默认非公平策略
public Semaphore(int permits) {
sync = new Sync(permits);
}
// 构造函数,创建一个具有给定许可数的Semaphore,并指定公平策略
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
// 获取一个许可,如果当前没有可用许可,则等待
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 释放一个许可
public void release() {
sync.releaseShared(1);
}
// ... 其他方法,如acquire(int), release(int), tryAcquire(), tryRelease(), drainPermits()等
// 同步器抽象类
abstract static class Sync extends AbstractQueuedSynchronizer {
// 返回当前许可数
final int getPermits() {
return getState();
}
// ... 其他方法,如tryAcquireShared(), tryReleaseShared()等
// 提供给子类实现的公平锁和非公平锁
// 这些方法需要由子类根据公平策略来实现
protected abstract boolean tryAcquireShared(int acquires);
protected abstract boolean tryReleaseShared(int releases);
// 减少许可数
final void reducePermits(int reductions) {
// ... 实现细节
}
// ... 其他方法
}
// 非公平同步器
static final class NonfairSync extends Sync {
// ... 实现tryAcquireShared()等方法
}
// 公平同步器
static final class FairSync extends Sync {
// ... 实现tryAcquireShared()等方法,保证FIFO顺序
}
}
源码解读:
Semaphore
使用AQS的同步状态来管理许可的数量。在Semaphore
中,同步状态表示当前可用的许可数。getState()
方法返回当前的许可数。
acquire()
方法时,它会尝试通过Sync
的acquireSharedInterruptibly()
方法获取一个许可。如果当前没有可用许可,线程将被阻塞。类似地,当线程调用release()
方法时,它会通过Sync
的releaseShared()
方法释放一个许可,这可能会唤醒正在等待的线程。
Semaphore
可以在创建时指定为公平或非公平。公平Semaphore
确保等待时间最长的线程优先获得许可,而非公平Semaphore
则不保证等待线程的获取顺序。这是通过FairSync
和NonfairSync
两个内部类来实现的,它们分别实现了tryAcquireShared()
和tryReleaseShared()
方法以提供不同的获取和释放策略。
Semaphore
通过扩展AQS并实现必要的方法来定义自己的同步语义。这包括实现tryAcquireShared()
和tryReleaseShared()
等方法,这些方法根据Semaphore
的当前状态和公平性策略来决定是否允许线程获取或释放许可。
Semaphore
类提供了几个主要的方法来操作许可证:
此外,Semaphore
还提供了带有超时参数的acquire
方法,允许线程在等待指定时间后放弃获取许可证。
我们使用Semaphore
来控制对资源访问,代码如下:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
// 定义一个Semaphore,初始许可数为3
private static final Semaphore semaphore = new Semaphore(3);
public static void main(String[] args) {
// 创建一个固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 模拟5个线程尝试访问一个只允许3个并发访问的资源
for (int i = 0; i < 5; i++) {
final int threadId = i;
executor.execute(() -> {
try {
// 获取一个许可
semaphore.acquire();
accessResource(threadId);
// 访问完成后释放许可
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池(这在实际应用中通常会在所有任务完成后进行)
// executor.shutdown();
}
// 模拟访问资源的方法
private static void accessResource(int threadId) throws InterruptedException {
System.out.println("线程 " + threadId + " 获取到许可,开始访问资源。");
// 模拟资源访问时间
Thread.sleep(1000);
System.out.println("线程 " + threadId + " 访问资源结束,释放许可。");
}
}
代码解释:
Semaphore
对象,其初始许可数设置为3。这意味着最多允许3个线程同时访问某个资源。
semaphore.acquire()
来获取一个许可。如果许可可用,线程将继续执行并访问资源;如果许可不可用(即已达到最大并发数),线程将被阻塞,直到有许可可用。
accessResource
方法中,我们模拟了线程访问资源的过程,通过Thread.sleep(1000)
来模拟资源访问所需的时间。
semaphore.release()
来释放许可,这样其他等待的线程就有机会获取许可并访问资源。
Semaphore
适用于以下场景:
Semaphore
。例如,数据库连接池或线程池。Semaphore
作为限流器。这可以防止系统过载并保持稳定的性能。Semaphore
可以实现更复杂的线程同步模式,如读写锁等。总之,
Semaphore
是Java并发编程中一个强大而灵活的同步工具。通过掌握其工作原理和特性,开发人员可以更好地应对多线程环境中的并发挑战。术因分享而日新,每获新知,喜溢心扉。 诚邀关注公众号 『
码到三十五
』 ,获取更多技术资料