在 Java 生产环境中,合理使用 线程池 和 信号量(Semaphore) 对保障系统稳定性、防止资源耗尽、控制并发度至关重要。下面分别从 最佳实践、配置建议 和 典型应用场景 详细说明。
一、生产环境中的线程池使用
1. 不要直接使用 Executors 工具类
虽然 Executors.newFixedThreadPool() 等方法简单易用,但在生产环境中存在严重隐患:
| 方法 | 问题 |
|---|---|
newFixedThreadPool / newSingleThreadExecutor | 使用无界队列 LinkedBlockingQueue,任务堆积可能导致 OOM |
newCachedThreadPool | 最大线程数为 Integer.MAX_VALUE,高并发下可能创建大量线程,导致系统崩溃 |
✅ 明确禁止
- 禁止使用
Executors创建线程池
原因:newFixedThreadPool使用无界队列,newCachedThreadPool允许无限线程,极易导致 OOM 或线程爆炸。
✅ 强制要求
- 必须通过
ThreadPoolExecutor显式构造 - 必须指定有界队列(如
ArrayBlockingQueue或带容量的LinkedBlockingQueue) - 必须自定义
ThreadFactory,命名规范(如xxx-pool-%d) - 必须设置合理的拒绝策略,并记录日志或告警
- 线程池需注册到统一监控平台(如 ARMS、鹰眼)
✅ 正确做法:使用 ThreadPoolExecutor 手动创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // corePoolSize:核心线程数
8, // maximumPoolSize:最大线程数
60L, // keepAliveTime:空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(100), // 有界队列(关键!)
new ThreadFactory() { // 自定义线程工厂(便于排查问题)
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "biz-pool-" + threadNumber.getAndIncrement());
t.setDaemon(false);
return t;
}
},
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略:由调用线程执行任务
);
2. 关键参数说明
| 参数 | 建议 |
|---|---|
corePoolSize | 根据 CPU 核心数和任务类型(CPU 密集型 or IO 密集型)设定。IO 密集型可设为 2 * CPU 核数 |
maximumPoolSize | 不宜过大,避免线程过多导致上下文切换开销 |
workQueue | 必须使用有界队列(如 ArrayBlockingQueue 或带容量的 LinkedBlockingQueue) |
RejectedExecutionHandler | 推荐使用 CallerRunsPolicy(降级执行)或自定义拒绝策略(记录日志、告警等) |
3. 监控与优雅关闭
- 监控:通过
executor.getActiveCount()、executor.getQueue().size()等方法暴露指标(可接入 Prometheus) - 关闭:
executor.shutdown(); // 不再接受新任务 try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); // 强制关闭 } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); }
二、生产环境中的信号量(Semaphore)使用
1. 适用场景
- 控制并发访问数量(如最多 10 个线程同时调用某外部 API)
- 资源池访问控制(如数据库连接池、文件句柄等)
- 限流(轻量级,适用于单机限流)
⚠️ 注意:
Semaphore是 单机限流,分布式场景需用 Redis + Lua 或 Sentinel 等方案。
2. 线程池 + Semaphore 联合使用示例
创建线程池
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
public class BoundedThreadPool {
private final ThreadPoolExecutor executor;
private final Semaphore semaphore;
private final long acquireTimeoutSeconds;
public BoundedThreadPool(int maxThreads, int queueCapacity, long acquireTimeoutSeconds) {
this.acquireTimeoutSeconds = acquireTimeoutSeconds;
// 总许可数 = 运行中线程 + 队列容量
this.semaphore = new Semaphore(maxThreads + queueCapacity);
this.executor = new ThreadPoolExecutor(
maxThreads,
maxThreads,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queueCapacity),
new ThreadFactory() {
private final AtomicLong count = new AtomicLong(0);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "biz-task-pool-" + count.getAndIncrement());
t.setDaemon(false);
return t;
}
},
new ThreadPoolExecutor.AbortPolicy() // 实际不会触发
);
}
/**
* 提交任务,支持返回结果和异常传播
*/
public <T> CompletableFuture<T> submit(Callable<T> task) {
CompletableFuture<T> future = new CompletableFuture<>();
boolean acquired;
try {
// ⚠️ 关键:带超时获取许可,防止永久阻塞
acquired = semaphore.tryAcquire(acquireTimeoutSeconds, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
future.completeExceptionally(new IllegalStateException("任务提交被中断", e));
return future;
}
if (!acquired) {
// 超时拒绝,返回明确错误
future.completeExceptionally(
new RejectedExecutionException(
"系统繁忙,任务提交等待超时(" + acquireTimeoutSeconds + "秒),请稍后重试")
);
return future;
}
// 许可已获取,提交到线程池
executor.execute(() -> {
try {
T result = task.call();
future.complete(result);
} catch (Throwable t) {
future.completeExceptionally(t);
} finally {
// 无论成功/失败/异常,必须释放许可!
semaphore.release();
}
});
return future;
}
public void shutdown() {
executor.shutdown();
try {
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
业务类
public class BusinessTask {
private final String requestId;
public BusinessTask(String requestId) {
this.requestId = requestId;
}
/**
* 按顺序执行 4 个业务场景,任一失败则整个任务终止
*/
public String execute() throws Exception {
System.out.println("[" + requestId + "] 开始执行任务");
try {
scene1();
scene2();
scene3();
scene4();
} catch (Exception e) {
System.err.println("[" + requestId + "] 任务失败: " + e.getMessage());
throw e; // 向上抛出,由 CompletableFuture 捕获
}
System.out.println("[" + requestId + "] 任务成功完成");
return "SUCCESS";
}
private void scene1() throws Exception {
System.out.println("[" + requestId + "] 执行场景1:数据校验");
simulateWork(300);
if (Math.random() < 0.1) throw new RuntimeException("场景1失败:参数非法");
}
private void scene2() throws Exception {
System.out.println("[" + requestId + "] 执行场景2:调用风控服务");
simulateWork(400);
if (Math.random() < 0.1) throw new RuntimeException("场景2失败:风控拒绝");
}
private void scene3() throws Exception {
System.out.println("[" + requestId + "] 执行场景3:生成订单");
simulateWork(500);
if (Math.random() < 0.1) throw new RuntimeException("场景3失败:库存不足");
}
private void scene4() throws Exception {
System.out.println("[" + requestId + "] 执行场景4:通知用户");
simulateWork(100);
if (Math.random() < 0.1) throw new RuntimeException("场景4失败:短信服务异常");
}
private void simulateWork(long millis) throws InterruptedException {
Thread.sleep(millis);
}
}
测试类
import org.springframework.http.ResponseEntity;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class TaskService {
// 生产环境建议通过 @Bean 注入
private static final BoundedThreadPool taskPool =
new BoundedThreadPool(
10, // 最大并发线程数
10, // 队列容量
3 // 提交等待超时:3秒
);
public ResponseEntity<?> process(TaskRequest request) {
String requestId = request.getRequestId();
System.out.println("[" + requestId + "] 开始处理任务");
if (requestId == null || requestId.trim().isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("error", "requestId 不能为空"));
}
// 提交任务
CompletableFuture<String> future = taskPool.submit(() -> {
BusinessTask task = new BusinessTask(requestId);
return task.execute(); // 可能抛出业务异常
});
try {
// ⚠️ 总任务执行超时:20秒(含排队+执行)
String result = future.get(20, TimeUnit.SECONDS);
return ResponseEntity.ok(Map.of(
"code", 200,
"message", "处理成功",
"result", result,
"requestId", requestId
));
} catch (TimeoutException e) {
return ResponseEntity.status(408).body(Map.of(
"code", 408,
"error", "任务处理超时(超过20秒)",
"requestId", requestId
));
} catch (ExecutionException e) {
// 获取原始业务异常
Throwable cause = e.getCause();
String errorMsg = cause != null ? cause.getMessage() : "未知系统错误";
System.out.println("[" + requestId + "] 业务处理失败: " + errorMsg);
return ResponseEntity.status(500).body(Map.of(
"code", 500,
"error", "业务处理失败: " + errorMsg,
"requestId", requestId
));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("[" + requestId + "] 请求处理被中断");
return ResponseEntity.status(503).body(Map.of(
"code", 503,
"error", "请求处理被中断",
"requestId", requestId
));
}
}
// 优雅关闭(Spring Boot 中可用 @PreDestroy)
// @PreDestroy
public void destroy() {
taskPool.shutdown();
}
public static void main(String[] args) {
TaskService taskService = new TaskService();
TaskRequest taskRequest = new TaskRequest();
for (int i = 0; i < 1000; i++) {
String requestId = "task-" + i;
taskRequest.setRequestId(requestId);
taskService.process(taskRequest);
}
taskService.destroy();
}
}
public class TaskRequest {
private String requestId;
public String getRequestId() { return requestId; }
public void setRequestId(String requestId) { this.requestId = requestId; }
}
执行结果如下:

三.Semaphore(信号量)和线程池(ThreadPoolExecutor)搭配使用,在 Java 并发编程中是一种精细化控制资源并发访问的高级组合策略。它们各自解决不同层面的问题,配合使用可以实现更安全、更灵活、更可控的并发模型。
一、各自职责回顾
| 组件 | 职责 | 控制粒度 |
|---|---|---|
| 线程池 | 管理线程生命周期,复用线程,控制任务执行的并发线程数 | 任务调度层面(有多少线程在跑) |
| Semaphore | 控制同时访问某类资源或执行某段逻辑的线程数量 | 业务逻辑层面(有多少线程能进某个“门”) |
✅ 简单说:
- 线程池决定“能同时跑多少任务”
- Semaphore 决定“在这些任务中,最多多少个能同时做某件高成本的事”
二、典型使用场景(为什么需要搭配?)
场景 1:线程池处理通用任务,但限制其中高成本操作的并发数
💡 例子:一个线程池处理 100 个用户请求,每个请求可能包含“调用第三方 OCR 服务”。OCR 接口有 QPS 限制(如最多 5 QPS),不能让所有线程同时调用。
// 线程池:处理所有请求
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 60, SECONDS, new LinkedBlockingQueue<>(100));
// 信号量:限制 OCR 并发为 5
Semaphore ocrPermit = new Semaphore(5);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
// 1. 做一些本地处理(无限制)
preprocess();
// 2. 调用 OCR(受信号量保护)
try {
if (ocrPermit.tryAcquire(2, SECONDS)) {
try {
callOCRApi(); // 受限操作
} finally {
ocrPermit.release();
}
} else {
log.warn("OCR request timeout, fallback...");
useFallback();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 3. 后续处理
postprocess();
});
}
✅ 优势:
- 线程池可以高效处理大量任务
- OCR 调用不会超限,避免被第三方限流或封禁
- 超时未获取许可时可降级(如返回缓存结果)
场景 2:控制对稀缺本地资源的访问
💡 例子:应用需要写入同一个日志文件(或操作同一个硬件设备),但文件 I/O 或设备驱动只支持最多 3 个并发写入。
Semaphore fileWriteSemaphore = new Semaphore(3); // 最多3个线程同时写
executor.submit(() -> {
try {
fileWriteSemaphore.acquire();
writeToFile(data);
} finally {
fileWriteSemaphore.release();
}
});
即使线程池有 50 个线程,也不会导致文件写入冲突或设备过载。
场景 3:实现“分阶段”并发控制
某些任务分为多个阶段,不同阶段有不同的并发限制。
Semaphore phase1 = new Semaphore(10); // 阶段1:最多10并发
Semaphore phase2 = new Semaphore(3); // 阶段2:最多3并发(如数据库写入)
executor.submit(() -> {
// 阶段1:数据拉取
phase1.acquire();
fetchData();
phase1.release();
// 阶段2:数据入库
phase2.acquire();
saveToDB();
phase2.release();
});
场景 4:防止线程池“虚假空闲”导致资源过载
有时线程池看似空闲(线程未满),但下游资源(如数据库连接)已耗尽。此时用 Semaphore 作为“资源许可证”更准确。
// 数据库连接池只有 8 个连接
Semaphore dbPermit = new Semaphore(8);
executor.submit(() -> {
dbPermit.acquire();
try (Connection conn = dataSource.getConnection()) { // 实际可能复用,此处仅为示意
executeSQL(conn);
} finally {
dbPermit.release();
}
});
这样即使线程池有 20 个线程,也不会申请超过 8 个 DB 连接。
三、搭配使用的核心价值
| 价值 | 说明 |
|---|---|
| 解耦调度与资源控制 | 线程池负责“任务怎么跑”,Semaphore 负责“资源怎么用” |
| 精细化限流 | 对特定操作限流,而非整个任务 |
| 避免资源过载 | 保护第三方服务、本地文件、硬件设备等 |
| 支持降级与超时 | tryAcquire(timeout) 可实现优雅降级 |
| 提升系统稳定性 | 防止单点资源成为系统瓶颈 |
四、注意事项
-
release()必须在finally中调用
防止异常导致许可证泄漏,造成死锁。 -
避免 Semaphore 成为性能瓶颈
如果许可数设置过小,会导致大量线程阻塞,反而降低吞吐。 -
不要用 Semaphore 替代线程池
Semaphore 不管理线程生命周期,大量线程仍会创建(如用new Thread().start()+ Semaphore),浪费资源。 -
公平性考虑
高并发下可考虑new Semaphore(permits, true)启用公平模式,避免线程饥饿。
五、总结
线程池 + Semaphore = “广度控制 + 深度控制”
- 线程池:控制整体并发规模(宏观)
- Semaphore:控制关键资源/操作的并发深度(微观)
在阿里、腾讯等大厂的高并发系统中,这种组合被广泛用于:
- 第三方 API 调用限流
- 数据库/缓存连接保护
- 文件/设备 I/O 控制
- 分布式任务中的本地资源协调
合理搭配,既能发挥线程池的高效调度能力,又能精准保护系统中的“脆弱环节”,是构建高可用 Java 应用的重要手段。



2049

被折叠的 条评论
为什么被折叠?



