Future 详解
1. Future 是什么?
Future 是 Java 中的一个接口(java.util.concurrent.Future
),代表异步计算的未来结果。它允许你:
- 提交任务后立即返回
- 在需要时检查任务是否完成
- 获取任务结果(完成后)
- 取消任务
2. 怎么使用 Future?
通过线程池提交任务:
ExecutorService executor = Executors.newFixedThreadPool(2);// 提交 Callable 任务(有返回值)
Future<String> future = executor.submit(() -> {TimeUnit.SECONDS.sleep(2);return "任务完成";
});// 其他操作...
String result = future.get(); // 阻塞直到结果就绪
3. Future 的作用
- 异步解耦:主线程不阻塞等待
- 结果获取:在需要时通过
get()
取结果 - 任务控制:支持取消和超时
4. Future 和异步的联系
Future 是 Java 中实现异步编程的核心工具:
- 提交任务即异步执行
- Future 对象作为"凭证",后续凭此获取结果
- 典型工作流:
5. 异步 vs 并发
异步 | 并发 |
---|---|
非阻塞调用 | 多个任务同时推进 |
关注单任务的非阻塞性 | 关注多任务管理 |
Future 是异步工具 | 线程/线程池是并发基础 |
例:网络IO不阻塞主线程 | 例:同时处理100个请求 |
6. Future 和线程的关系
- 依赖关系:Future 需要线程池执行任务
- 结果载体:线程池返回 Future 作为结果容器
- 控制中介:通过 Future 控制任务状态(如取消)
7. 复杂任务处理:Future 组合案例
ExecutorService executor = Executors.newFixedThreadPool(3);// 步骤1:获取用户数据(耗时2秒)
Future<String> userFuture = executor.submit(() -> {TimeUnit.SECONDS.sleep(2);return "用户数据";
});// 步骤2:获取订单数据(依赖用户数据)
Future<String> orderFuture = executor.submit(() -> {String user = userFuture.get(); // 阻塞等待前置任务return user + " + 订单数据";
});// 步骤3:获取推荐数据(独立任务)
Future<String> recommendFuture = executor.submit(() -> {TimeUnit.SECONDS.sleep(1);return "推荐数据";
});// 组合最终结果
String result = orderFuture.get() + " | " + recommendFuture.get();
System.out.println("最终结果: " + result); // 用户数据 + 订单数据 | 推荐数据executor.shutdown();
代码解析:
- 线程池管理:使用 3 线程池处理并发
- 任务依赖:
orderFuture
依赖userFuture.get()
阻塞等待recommendFuture
独立执行
- 结果合并:
- 总耗时 ≈ 最大路径:
- 路径A→B:2秒
- 路径C:1秒
- 最终耗时 ≈ 2秒(非3秒)
执行流程:
timelinetitle 任务时间轴(单位:秒)section 线程1用户任务 : 0 - 2订单任务 : 2 - 2(瞬时完成)section 线程2空闲 : 0 - 1订单等待 : 1 - 2section 线程3推荐任务 : 0 - 1
关键点:Future 适合简单依赖,对于复杂依赖链建议使用
CompletableFuture
(支持回调、链式操作)
总结
- Future 本质:异步任务的结果容器
- 核心价值:分离任务提交与结果获取
- 适用场景:IO密集型任务、并行计算、服务组合
- 进阶建议:Java 8+ 使用
CompletableFuture
增强异步编程能力
线程池深度解析
一、线程池是什么?
线程池(Thread Pool)是一种线程管理机制,它预先创建一组可复用的线程,通过任务队列管理待执行任务。其核心组件包括:
二、线程池的作用
作用维度 | 具体说明 | 优势对比 |
---|---|---|
资源复用 | 避免频繁创建/销毁线程 | 创建线程成本:约1ms/次 vs 复用成本:≈0 |
流量控制 | 通过队列缓冲突发请求 | 避免服务器过载崩溃 |
性能提升 | 减少线程切换开销 | 实测:线程复用比新建快5-10倍 |
统一管理 | 提供状态监控/任务取消 | 比单个线程更易监控控制 |
三、线程池 vs 线程的关系
维度 | 线程池 | 独立线程 |
---|---|---|
本质 | 线程资源管理器 | 执行的最小单位 |
生命周期 | 长期驻留(可复用) | 执行完立即销毁 |
关系比喻 | 企业的人力资源部 | 具体干活的员工 |
创建成本 | 一次性创建多次使用 | 每次任务都新建 |
使用场景 | 高并发任务(1000+) | 简单单次任务 |
四、线程池核心参数
Java中创建线程池的完整参数:
ThreadPoolExecutor(int corePoolSize, // 常驻核心线程数int maximumPoolSize, // 最大线程数long keepAliveTime, // 空闲线程存活时间TimeUnit unit, // 时间单位BlockingQueue<Runnable> workQueue, // 任务队列ThreadFactory threadFactory, // 线程创建工厂RejectedExecutionHandler handler // 拒绝策略
)
五、线程池执行流程
六、四种拒绝策略对比
策略类型 | 触发条件 | 处理方式 | 适用场景 |
---|---|---|---|
AbortPolicy | 队列和线程池全满 | 抛出RejectedException | 严格要求不丢任务 |
CallerRunsPolicy | 同上 | 回退给提交者线程执行 | 核心业务场景 |
DiscardPolicy | 同上 | 静默丢弃新任务 | 日志采集等非关键任务 |
DiscardOldestPolicy | 同上 | 丢弃队首任务并重试 | 实时性要求高场景 |
七、线程池使用黄金法则
-
严禁使用Executors快捷方法
- 问题:
newFixedThreadPool
使用无界队列 → 可能导致OOM - 正确:手动创建
ThreadPoolExecutor
- 问题:
-
合理设置队列容量
- 计算型任务:队列长度设为 2×核心线程数
- IO密集型:可设置稍大(但需监控队列堆积)
-
监控关键指标
// 重要监控指标 executor.getActiveCount(); // 活跃线程数 executor.getQueue().size(); // 队列积压量 executor.getCompletedTaskCount(); // 完成数量
-
线程池关闭姿势
executor.shutdown(); // 平缓关闭 if(!executor.awaitTermination(60, SECONDS)) {executor.shutdownNow(); // 强制关闭 }
八、实际应用场景
- Web服务器:Tomcat线程池(
maxThreads=200
+acceptCount=100
) - 大数据处理:Spark任务调度池
- 金融交易:独立线程池处理不同优先级订单
- 微服务架构:Hystrix线程池隔离不同服务调用
通过合理使用线程池,某电商系统性能对比:
指标 | 未用线程池 | 优化后线程池 | 提升 |
---|---|---|---|
QPS | 1200 | 5600 | 367% |
CPU波动 | 20%-95% | 65%-75% | 更平稳 |
响应时间 | 300±250ms | 50±15ms | 降83% |