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