Java并发神器Semaphore全方位解析

在现代的Java开发中,高并发已经成为了一个不可忽视的话题。而在众多的并发工具中,Semaphore 可以说是一个既强大又灵活的工具。今天,我们就来深入探讨一下 Semaphore 的底层原理、实现机制、实际应用场景以及项目实战代码。准备好了吗?让我们开始这场并发之旅吧!

2024最全大厂面试题无需C币点我下载或者在网页打开全套面试题已打包

AI绘画关于SD,MJ,GPT,SDXL,Comfyui百科全书

1. Semaphore是什么?

Semaphore(信号量)是一个用于控制同时访问特定资源的线程数量的同步工具。它通过维护一个许可集来管理对资源的访问。线程在访问资源之前必须从信号量中获取许可,访问完成后释放许可。如果没有可用的许可,线程将被阻塞,直到有可用的许可为止。

1.1 Semaphore的基本用法

在Java中,Semaphore 类位于 java.util.concurrent 包中。它的构造方法主要有以下两种:

  1. Semaphore(int permits):创建一个具有指定许可数的 Semaphore
  2. 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应用程序,然后使用工具(如 curlPostman)发送多个并发请求到 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 添加监控和报警(续)

我们可以通过使用 MicrometerPrometheus 来监控 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);
    }
}
配置 PrometheusGrafana

src/main/resources/application.yml 中添加以下配置:

management:
  endpoints:
    web:
      exposure:
        include: "*"
  metrics:
    export:
      prometheus:
        enabled: true
  endpoint:
    prometheus:
      enabled: true
使用 PrometheusGrafana 进行监控
  1. 启动 Prometheus

    • 安装并配置 Prometheus,确保其能够从Spring Boot应用的 /actuator/prometheus 端点抓取数据。
    scrape_configs:
      - job_name: 'spring-boot'
        static_configs:
          - targets: ['localhost:8080']
    
  2. 启动 Grafana

    • 安装并配置 Grafana,添加 Prometheus 作为数据源。
    • 创建仪表板,使用 semaphore.available.permitssemaphore.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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值