请教:我的MysqlDAOFactory单例模式实现是否正确?
你的MysqlDAOFactory单例实现问题分析及修正建议
当前实现的核心问题
- 线程不安全:
connection()方法的懒加载逻辑没有同步控制,多线程环境下会有多个线程同时进入if(f == null)分支,导致创建多个MysqlDAOFactory实例,违背单例设计的初衷。 - EntityManager线程安全风险:
EntityManager本身不是线程安全的,你把同一个em实例传递给所有PlaneDAOImpl,当多个线程同时调用DAO方法时,会引发并发访问异常。 - 冗余的@Autowired注解:这个类是通过
new关键字手动实例化的,不属于Spring容器管理对象,@Autowired注解不会生效,完全可以移除。 - 单例初始化隐患:静态变量
f没有用volatile修饰,可能因JVM指令重排导致其他线程读取到半初始化的实例。
修正后的实现方案
方案1:双重检查锁(DCL)实现线程安全单例,修复EntityManager问题
public class MysqlDAOFactory { // 用volatile禁止指令重排,避免半初始化实例被读取 private static volatile MysqlDAOFactory instance; // EntityManagerFactory是线程安全的,作为单例持有 private final EntityManagerFactory emf; private MysqlDAOFactory() { Persistence.generateSchema("jpa", null); emf = Persistence.createEntityManagerFactory("jpa"); } public static MysqlDAOFactory getInstance() { if (instance == null) { synchronized (MysqlDAOFactory.class) { if (instance == null) { instance = new MysqlDAOFactory(); } } } return instance; } public PlaneDAO getPlaneDAOImpl() { // 每次获取DAO时创建新的EntityManager,避免线程安全问题 EntityManager em = emf.createEntityManager(); return new PlaneDAOImpl(em); } }
方案2:枚举单例(简洁且安全,推荐)
枚举单例天然线程安全,还能防止反射破坏单例特性,同时处理EntityManager的线程安全问题:
public enum MysqlDAOFactory { INSTANCE; private final EntityManagerFactory emf; MysqlDAOFactory() { Persistence.generateSchema("jpa", null); emf = Persistence.createEntityManagerFactory("jpa"); } public PlaneDAO getPlaneDAOImpl() { EntityManager em = emf.createEntityManager(); return new PlaneDAOImpl(em); } }
使用时直接调用MysqlDAOFactory.INSTANCE.getPlaneDAOImpl()即可。
额外建议
如果你的项目已经集成Spring,更推荐让Spring管理DAO和EntityManager的生命周期:
- 给
PlaneDAOImpl添加@Repository注解,用@PersistenceContext注入EntityManager - 直接通过Spring容器获取DAO实例,无需手动实现DAO工厂,简化代码同时避免单例管理的麻烦
内容的提问来源于stack exchange,提问作者pep02
相关产品推荐
相关产品推荐

