在 synchronized 块中直接调用 entitymanager 不安全,因其非线程安全且生命周期绑定事务;应使用短生命周期、事务级获取的 em,配合乐观锁、数据库 where 条件校验及 spring 的 @persistencecontext 代理实现线程安全。

在 synchronized 块中直接调用 JPA 的 EntityManager 是不安全的,主要原因在于 EntityManager 本身不是线程安全的,且其生命周期与事务、持久化上下文强绑定。强行在同步块中复用或跨线程共享 EntityManager 实例,极易引发 IllegalStateException、脏读、缓存不一致、事务失效甚至连接泄漏等问题。
避免在 synchronized 块中持有 EntityManager 实例
EntityManager 应该是短生命周期、方法级(或事务级)获取的对象,而非被多个线程争抢的共享资源。synchronized 块通常用于协调业务逻辑的并发访问(比如库存扣减),但数据操作仍应交由容器管理的、线程隔离的 EM 完成:
- 不要把
EntityManager声明为类成员变量并在 synchronized 块里反复调用 - 不要在 synchronized 块内手动调用
em.flush()或em.clear()来“规避”问题——这掩盖了设计缺陷 - 同步范围应尽量小,仅包裹真正需要串行化的业务判断(如“检查余额是否充足”),而不是整个数据库操作
用事务 + 乐观锁替代粗粒度同步
多数需要 synchronized 的场景(如并发下单、抢购)本质是防止超卖,推荐用数据库层面的并发控制,而非 JVM 级同步:
- 给实体添加
@Version字段启用乐观锁,JPA 在merge或flush时自动校验版本号,冲突时抛出OptimisticLockException - 关键更新语句使用原生 SQL + WHERE 条件(如
UPDATE product SET stock = stock - 1 WHERE id = ? AND stock >= 1),返回影响行数判断是否成功 - 配合 Spring 的
@Transactional,确保数据库操作在事务中执行,失败时自动回滚
若必须同步,确保 EntityManager 获取与使用完全隔离
极端情况下(如遗留系统无法改造),需保证每个线程都拥有独立的、正确绑定的 EntityManager:
- 在 synchronized 块 内部 调用
EntityManagerFactory.createEntityManager()获取新实例(注意:必须手动close()) - 更稳妥的做法是:先在 synchronized 块中完成业务决策(如生成订单号、校验资格),再退出同步块,最后用 Spring 注入的
EntityManager(或EntityManagerFactory)执行持久化 - 绝对不要把
createEntityManager()得到的实例缓存或跨方法传递
Spring 环境下的推荐实践
在 Spring 中,应依赖框架对 JPA 的集成,而非手动管理:
- 使用
@PersistenceContext注入的EntityManager—— 它是代理对象,每次调用都会路由到当前事务绑定的实际实例,天然线程安全 - 将并发敏感逻辑拆分为
@Service方法,并用@Transactional控制事务边界;必要时加@Retryable自动重试乐观锁失败 - 高并发场景可结合 Redis 分布式锁(如
RedisTemplate.opsForValue().setIfAbsent())做前置校验,把竞争从 DB 层转移到缓存层
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











