必须用ExecutorService管理任务生命周期,禁用new Thread();生产环境显式构造ThreadPoolExecutor并配置有界队列与CallerRunsPolicy;状态需持久化至数据库并事务更新;任务中定期检查中断并双重校验取消;CompletableFuture编排依赖时避免join()阻塞主线程;MyBatis插入用useGeneratedKeys安全获取主键。

用 ExecutorService 管理任务生命周期,别直接 new Thread()
手动创建 Thread 对象启动任务会导致资源失控、无法统一回收、异常丢失。实际项目中必须用线程池封装执行逻辑。
推荐使用 ThreadPoolExecutor 或其工厂类 Executors(注意:仅限开发/测试快速验证,生产环境应显式构造以避免 LinkedBlockingQueue 无界队列引发 OOM):
ExecutorService executor = new ThreadPoolExecutor(
2, 4, 30L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadFactoryBuilder().setNameFormat("task-pool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);-
CallerRunsPolicy是关键兜底策略:当队列满且线程数已达上限时,由提交任务的线程自己执行该任务,防止任务无限积压 - 务必调用
executor.shutdown()+awaitTermination()做优雅停机,否则 JVM 可能不退出 - 每个任务应捕获并处理
Throwable,否则未检查异常会静默吞掉,导致任务“消失”
任务状态用枚举 + 数据库字段联合建模,别只靠内存标记
仅用 volatile boolean isRunning 这类内存变量无法支撑重启恢复、多实例协同或审计需求。必须将状态持久化到数据库,并与内存状态做同步校验。
典型表结构建议包含:id、status(TASK_STATUS 枚举:PENDING / RUNNING / SUCCESS / FAILED / CANCELLED)、created_at、started_at、finished_at、error_message(TEXT 类型,存异常堆栈摘要):
立即学习“Java免费学习笔记(深入)”;
- 状态变更必须走事务:先更新 DB 的
status,再触发业务逻辑;失败则回滚,避免状态漂移 - 轮询检查任务进度时,不要依赖
SELECT * FROM task WHERE status = 'RUNNING'全表扫描,加WHERE updated_at > ?时间范围过滤 - 若支持取消,需在任务执行体中定期检查
Thread.currentThread().isInterrupted(),并配合 DB 中status = 'CANCELLED'双重判断
用 CompletableFuture 编排依赖任务,但慎用 join()
当任务存在先后依赖(如“导出报表”需等“生成数据”完成),CompletableFuture 比手动维护回调链更清晰。但它的阻塞方法容易埋雷。
- 避免在 Web 请求线程中调用
cf.join()或cf.get():会阻塞 Tomcat/NIO 线程,迅速拖垮吞吐量 - 用
thenApplyAsync(..., executor)显式指定异步执行器,确保后续步骤不挤占主线程资源 - 组合多个任务时,优先用
allOf()+thenAccept()而非循环join(),前者是真正并行等待,后者是串行阻塞 - 异常传播要主动处理:
exceptionally()或handle()必须覆盖,否则上游无法感知下游失败
MyBatis 插入任务记录时,用 useGeneratedKeys 获取主键,别查两次
插入新任务后通常要立即拿到 ID 去更新状态或关联子任务。如果先 INSERT 再 SELECT LAST_INSERT_ID(),在高并发下可能拿错 ID(尤其 MySQL 使用 REPLACE INTO 或批量插入场景)。
MyBatis 正确做法是在 标签中声明:
INSERT INTO task (status, created_at) VALUES (#{status}, #{createdAt})
-
keyProperty="id"必须与实体类字段名完全一致(区分大小写),否则赋值失败且无报错 - PostgreSQL 需额外指定
keyColumn="id",因为其序列名和字段名常不一致 - 批量插入时
useGeneratedKeys仍有效,但要求 JDBC 驱动版本 ≥ 4.2 且数据库配置允许返回多行生成键
跨服务或长时间运行任务的状态同步,不能只靠单库事务。最终一致性才是现实选择——比如用本地消息表 + 定时扫描,而不是强求所有操作包在一个 @Transactional 里。










