activiti 不依赖 synchronized 保障流程状态机线程安全,而是通过数据库事务与乐观锁(rev_ 版本号校验)实现;synchronized 在集群环境下无效,仅适用于单机非核心路径的本地缓存操作。

如果你在自定义监听器、任务委托或流程变量操作中手动使用 synchronized,需特别注意:它只在单 JVM 进程内有效,而生产环境的 Activiti 通常部署为集群(多节点),此时 synchronized 完全失效,无法防止并发状态冲突。
为什么不能靠 synchronized 保护流程状态切换
Activiti 的流程实例状态(如 ACT_RU_EXECUTION 中的 STATE_ 字段)本质是数据库记录。状态变更发生在 Service 层调用 runtimeService.signal() 或 taskService.complete() 时,底层走的是 MyBatis + 数据库事务 + SELECT ... FOR UPDATE 或乐观锁(REV_ 版本号校验)。JVM 锁对此无感知。
- 多个应用节点同时触发同一流程实例的信号,
synchronized(this)或synchronized(ProcessInstance.class)只锁住各自进程内的对象,互不影响 - 数据库才是唯一真实的状态源;synchronized 无法阻止两个节点同时读到旧状态、各自计算后写回,造成覆盖或状态错乱
- Activiti 内部已通过
ExecutionEntity的lock()方法配合数据库行锁实现并发控制,重复加 JVM 锁既冗余又误导
真正起作用的并发控制机制
Activiti 采用“数据库事务 + 乐观锁”组合保障状态一致性:
- 每次更新执行流(
Execution)或任务(Task)时,SQL 自动带上WHERE REV_ = ?条件 - 若版本号不匹配(说明已被其他事务修改),抛出
OptimisticLockingException,框架会回滚并可重试 - 关键操作如
signalEventReceived或completeTask默认包裹在PROPAGATION_REQUIRED事务中,确保原子性
什么场景下可以(谨慎)用 synchronized
仅限单机部署且满足以下全部条件时,才考虑在**非核心路径**加 JVM 锁:
- 你正在编写一个本地缓存更新逻辑(例如:流程启动后同步写入本地 Guava Cache)
- 该缓存只被当前 JVM 使用,不跨节点共享
- 锁对象是私有 final 的(如
private final Object cacheLock = new Object();),避免锁膨胀或误共享 - 绝不用于包裹
runtimeService或taskService调用——这些必须交由数据库保证
替代方案:更安全的分布式协调方式
若需在集群中协调流程行为(如限制某类流程并发数、防重复触发),应放弃 synchronized,改用:
-
数据库唯一约束:在业务表加
UNIQUE(processKey, businessId),插入前尝试写入防重记录 -
Redis 分布式锁(如 Redisson 的
RLock),配合超时与看门狗,锁粒度对齐流程实例 ID -
Activiti 自带的事件总线:监听
EVENT_TYPE_ACTIVITY_COMPLETED后做幂等处理,而非前置加锁
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











