在现代的Java开发中,高并发已经成为了一个不可忽视的话题。而在众多的并发工具中,Semaphore
可以说是一个既强大又灵活的工具。今天,我们就来深入探讨一下 Semaphore
的底层原理、实现机制、实际应用场景以及项目实战代码。准备好了吗?让我们开始这场并发之旅吧!
2024最全大厂面试题无需C币点我下载或者在网页打开全套面试题已打包
AI绘画关于SD,MJ,GPT,SDXL,Comfyui百科全书
1. Semaphore是什么?
Semaphore
(信号量)是一个用于控制同时访问特定资源的线程数量的同步工具。它通过维护一个许可集来管理对资源的访问。线程在访问资源之前必须从信号量中获取许可,访问完成后释放许可。如果没有可用的许可,线程将被阻塞,直到有可用的许可为止。
1.1 Semaphore的基本用法
在Java中,Semaphore
类位于 java.util.concurrent
包中。它的构造方法主要有以下两种:
- Semaphore(int permits):创建一个具有指定许可数的
Semaphore
。 - Semaphore(int permits, boolean fair):创建一个具有指定许可数且具有公平性选择的
Semaphore
。
- 公平性:如果公平性设为
true
,则线程将按先来先得的顺序获取许可。
Semaphore semaphore = new Semaphore(3); // 创建一个具有3个许可的信号量
2. Semaphore底层原理
2.1 基本原理
Semaphore
的核心在于许可的管理,许可表示资源的访问权限。每个信号量维护一个内部计数器,该计数器表示当前可用的许可数量。
- 当线程调用
acquire()
方法时,如果计数器大于0,则减1并继续执行;如果计数器为0,则线程进入等待状态,直到有其他线程释放许可。 - 当线程调用
release()
方法时,计数器加1,如果有等待的线程,则唤醒其中一个。
2.2 主要方法
- acquire():获取一个许可,如果没有可用许可,则等待。
- release():释放一个许可,增加可用许可数。
- tryAcquire():尝试获取一个许可,如果成功则返回
true
,否则返回false
。 - availablePermits():返回当前可用的许可数。
Semaphore semaphore = new Semaphore(3);
try {
semaphore.acquire(); // 获取许可
// 执行需要控制并发的方法
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
2.3 底层实现
Semaphore
的底层实现主要依赖于 AQS
(AbstractQueuedSynchronizer)。AQS
是一个用于构建锁和同步器的框架,提供了FIFO队列来管理线程的等待状态。
2.3.1 AQS简介
AQS
通过内部的一个 state
变量来维护同步状态:
- state > 0:表示有可用的许可。
- state == 0:表示没有可用的许可。
2.3.2 Semaphore内部类
Semaphore
通过继承 AQS
并重写其方法来实现许可管理。其内部类主要有以下两个:
- NonfairSync:非公平模式的同步器。
- FairSync:公平模式的同步器。
2.3.3 acquire() 的实现
当线程调用 acquire()
方法时,Semaphore
会尝试获取许可,如果 state > 0
则直接减1,否则将当前线程加入等待队列。
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 || compareAndSetState(available, remaining))
return remaining;
}
}
2.3.4 release() 的实现
当线程调用 release()
方法时,Semaphore
会增加 state
的值,并唤醒等待队列中的一个线程。
好的,让我们继续深入探讨 Semaphore
的底层原理和实际应用场景。
public void release() {
sync.releaseShared(1);
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow check
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
在这个实现中,tryReleaseShared
方法通过一个无限循环尝试增加 state
,并使用 compareAndSetState
确保线程安全。
3. Semaphore的实际应用场景
Semaphore
在实际开发中有很多应用场景,特别是在需要限制资源访问数量的地方。以下是几个常见的应用场景:
3.1 限制数据库连接数
在高并发环境中,数据库连接是宝贵的资源,过多的连接可能导致数据库压力过大,影响性能。使用 Semaphore
可以限制同时访问数据库的连接数。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class DatabaseConnectionLimiter {
private static final int MAX_CONNECTIONS = 5;
private static final Semaphore semaphore = new Semaphore(MAX_CONNECTIONS);
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
try {
semaphore.acquire();
accessDatabase();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
}
executor.shutdown();
}
private static void accessDatabase() {
System.out.println(Thread.currentThread().getName() + " accessing database...");
try {
Thread.sleep(2000); // 模拟数据库操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " done accessing database.");
}
}
3.2 控制并发执行的任务数量
在一些场景中,我们需要限制并发执行的任务数量,以防止系统过载。例如,限制同时处理的文件数量。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class FileProcessor {
private static final int MAX_CONCURRENT_TASKS = 3;
private static final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_TASKS);
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
try {
semaphore.acquire();
processFile();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
});
}
executor.shutdown();
}
private static void processFile() {
System.out.println(Thread.currentThread().getName() + " processing file...");
try {
Thread.sleep(3000); // 模拟文件处理
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " done processing file.");
}
}
3.3 控制资源池的访问
Semaphore
也可以用于控制对资源池的访问,例如对象池、线程池等。通过限制同时访问资源池的线程数量,避免资源争用和性能下降。
import java.util.concurrent.Semaphore;
public class ResourcePool {
private static final int MAX_RESOURCES = 5;
private static final Semaphore semaphore = new Semaphore(MAX_RESOURCES);
private static final Resource[] resources = new Resource[MAX_RESOURCES];
static {
for (int i = 0; i < MAX_RESOURCES; i++) {
resources[i] = new Resource(i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Resource resource = null;
try {
semaphore.acquire();
resource = getResource();
useResource(resource);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
releaseResource(resource);
semaphore.release();
}
}).start();
}
}
private static Resource getResource() {
for (Resource resource : resources) {
if (!resource.isInUse()) {
resource.setInUse(true);
return resource;
}
}
return null;
}
private static void useResource(Resource resource) {
System.out.println(Thread.currentThread().getName() + " using resource " + resource.getId());
try {
Thread.sleep(2000); // 模拟资源使用
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " done using resource " + resource.getId());
}
private static void releaseResource(Resource resource) {
if (resource != null) {
resource.setInUse(false);
}
}
static class Resource {
private final int id;
private boolean inUse;
public Resource(int id) {
this.id = id;
this.inUse = false;
}
public int getId() {
return id;
}
public boolean isInUse() {
return inUse;
}
public void setInUse(boolean inUse) {
this.inUse = inUse;
}
}
}
在这个示例中,我们创建了一个资源池,并使用 Semaphore
来限制同时访问资源池的线程数量。每个线程在获取资源后进行使用,使用完毕后释放资源。
4. Semaphore在项目中的实战应用
为了让大家更好地理解 Semaphore
的实际应用,我们来做一个更复杂的项目实战示例。假设我们有一个Web服务,需要处理大量的请求,每个请求都需要访问一个第三方API。为了防止超出第三方API的访问限制,我们需要控制并发请求的数量。
4.1 示例项目结构
我们将创建一个简单的Web服务,使用 Spring Boot
框架,并在其中使用 Semaphore
来控制并发请求的数量。
项目结构如下:
semaphore-demo
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── semaphoredemo
│ │ │ ├── SemaphoreDemoApplication.java
│ │ │ ├── controller
│ │ │ │ └── ApiController.java
│ │ │ └── service
│ │ │ └── ApiService.java
│ │ └── resources
│ │ └── application.yml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── semaphoredemo
│ └── SemaphoreDemoApplicationTests.java
4.2 创建Spring Boot项目
首先,创建一个Spring Boot项目,并添加以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
4.3 编写ApiService类
ApiService
类负责处理对第三方API的请求,并使用 Semaphore
来控制并发请求的数量。
package com.example.semaphoredemo.service;
import org.springframework.stereotype.Service;
import java.util.concurrent.Semaphore;
@Service
public class ApiService {
private static final int MAX_CONCURRENT_REQUESTS = 5;
private final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_REQUESTS);
public String callExternalApi() throws InterruptedException {
semaphore.acquire();
try {
// 模拟调用第三方API
Thread.sleep(2000); // 模拟API响应时间
return "Response from external API";
} finally {
semaphore.release();
}
}
}
4.4 编写ApiController类
ApiController
类提供一个HTTP端点,通过调用 ApiService
来处理请求。
package com.example.semaphoredemo.controller;
import com.example.semaphoredemo.service.ApiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ApiController {
@Autowired
private ApiService apiService;
@GetMapping("/api")
public String callApi() {
try {
return apiService.callExternalApi();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "Failed to call external API";
}
}
}
4.5 编写主应用程序类
SemaphoreDemoApplication
是我们的主应用程序类,负责启动Spring Boot应用。
package com.example.semaphoredemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SemaphoreDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SemaphoreDemoApplication.class, args);
}
}
4.6 配置文件
在 src/main/resources/application.yml
中,我们可以配置一些Spring Boot的默认设置。
server:
port: 8080
spring:
application:
name: semaphore-demo
4.7 测试
我们可以创建一个简单的测试类来确保我们的应用程序能够正确处理并发请求。
package com.example.semaphoredemo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SemaphoreDemoApplicationTests {
@Test
void contextLoads() {
}
}
4.8 运行和测试
运行我们的Spring Boot应用程序,然后使用工具(如 curl
或 Postman
)发送多个并发请求到 http://localhost:8080/api
。我们可以观察到,即使我们发送了超过5个并发请求,Semaphore
也会限制同时处理的请求数量为5个,剩余的请求会被阻塞直到有可用的许可。
# 使用curl进行并发请求测试
for i in {1..10}; do
curl -X GET "http://localhost:8080/api" &
done
wait
5. 进一步优化和扩展
5.1 超时机制
我们可以为 Semaphore
添加超时机制,以防止线程无限期等待。
public String callExternalApi() throws InterruptedException {
if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
try {
// 模拟调用第三方API
Thread.sleep(2000); // 模拟API响应时间
return "Response from external API";
} finally {
semaphore.release();
}
} else {
return "Failed to acquire semaphore - timeout";
}
}
5.2 动态调整并发限制
我们可以通过配置文件或管理端点动态调整 Semaphore
的许可数量。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "semaphore")
public class SemaphoreConfig {
private int maxConcurrentRequests = 5;
public int getMaxConcurrentRequests() {
return maxConcurrentRequests;
}
public void setMaxConcurrentRequests(int maxConcurrentRequests) {
this.maxConcurrentRequests = maxConcurrentRequests;
}
}
在 ApiService
中使用 SemaphoreConfig
:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.Semaphore;
@Service
public class ApiService {
private final Semaphore semaphore;
@Autowired
public ApiService(SemaphoreConfig semaphoreConfig) {
this.semaphore = new Semaphore(semaphoreConfig.getMaxConcurrentRequests());
}
public String callExternalApi() throws InterruptedException {
semaphore.acquire();
try {
// 模拟调用第三方API
Thread.sleep(2000);
return "Response from external API";
} finally {
semaphore.release();
}
}
}
在 application.yml
中添加配置:
semaphore:
max-concurrent-requests: 5
5.3 添加监控和报警
我们可以集成监控工具(如Prometheus和Grafana)来监控 Semaphore
的使用情况,并在并发请求过多时发送报警。
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class ApiService implements MeterBinder {
private final Semaphore semaphore;
@Autowired
public ApiService(SemaphoreConfig semaphoreConfig) {
this.semaphore = new Semaphore(semaphoreConfig.getMaxConcurrentRequests());
}
@Timed("api.call")
public String callExternalApi() throws InterruptedException {
semaphore.acquire();
try {
// 模拟调用第三方API
Thread.sleep(2000);
return "Response from external API";
} finally {
semaphore.release();
}
}
5.3 添加监控和报警(续)
我们可以通过使用 Micrometer
和 Prometheus
来监控 Semaphore
的使用情况,并在并发请求过多时发送报警。首先,我们需要添加依赖项:
在 pom.xml
中添加以下依赖:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置 ApiService
进行监控
我们可以通过 MeterRegistry
记录 Semaphore
的使用情况。以下是增强后的 ApiService
:
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.MeterBinder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@Service
public class ApiService implements MeterBinder {
private final Semaphore semaphore;
@Autowired
public ApiService(SemaphoreConfig semaphoreConfig) {
this.semaphore = new Semaphore(semaphoreConfig.getMaxConcurrentRequests());
}
public String callExternalApi() throws InterruptedException {
if (semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
try {
// 模拟调用第三方API
Thread.sleep(2000);
return "Response from external API";
} finally {
semaphore.release();
}
} else {
return "Failed to acquire semaphore - timeout";
}
}
@Override
public void bindTo(MeterRegistry registry) {
registry.gauge("semaphore.available.permits", semaphore, Semaphore::availablePermits);
registry.gauge("semaphore.queue.length", semaphore, Semaphore::getQueueLength);
}
}
配置 Prometheus
和 Grafana
在 src/main/resources/application.yml
中添加以下配置:
management:
endpoints:
web:
exposure:
include: "*"
metrics:
export:
prometheus:
enabled: true
endpoint:
prometheus:
enabled: true
使用 Prometheus
和 Grafana
进行监控
-
启动 Prometheus:
- 安装并配置 Prometheus,确保其能够从Spring Boot应用的
/actuator/prometheus
端点抓取数据。
scrape_configs: - job_name: 'spring-boot' static_configs: - targets: ['localhost:8080']
- 安装并配置 Prometheus,确保其能够从Spring Boot应用的
-
启动 Grafana:
- 安装并配置 Grafana,添加 Prometheus 作为数据源。
- 创建仪表板,使用
semaphore.available.permits
和semaphore.queue.length
指标进行监控。
5.4 添加熔断和限流
为了更好地保护系统,我们可以集成熔断器和限流机制,如 Resilience4j
。
添加 Resilience4j
依赖
在 pom.xml
中添加以下依赖:
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-circuitbreaker</artifactId>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-ratelimiter</artifactId>
</dependency>
配置 Resilience4j
在 src/main/resources/application.yml
中添加以下配置:
resilience4j:
circuitbreaker:
instances:
apiService:
registerHealthIndicator: true
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 10000
ratelimiter:
instances:
apiService:
limitForPeriod: 10
limitRefreshPeriod: 1s