You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

优惠券系统单例ConnectionPool重复实例化致连接翻倍问题求助

单例模式失效导致ConnectionPool被实例化两次的问题排查与解决

问题回顾

我正在开发一个优惠券系统,其中ConnectionPool类采用双重检查锁单例模式设计,首次获取实例时应创建10个数据库连接,并打印10次"Creating connection #num"。我的DBDAO类(比如CompaniesDBDAO)同样为单例,内部持有ConnectionPool的私有实例,但测试时控制台却打印了20次连接创建信息,且ConnectionPool被实例化了2次,特此寻求帮助。

相关代码片段

ConnectionPool 代码

package com.app.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Stack;
import com.app.db.DBManager;
public class ConnectionPool {
 private Stack<Connection> connections = new Stack<Connection>();
 private static ConnectionPool instance = null;
 private ConnectionPool() {
 for (int i = 1; i <= 10; i++) {
 System.out.println("Creating connection #" + i);
 try {
 Connection conn = DriverManager.getConnection(DBManager.getUrl(), DBManager.getUser(), DBManager.getPass());
 connections.push(conn);
 } catch (SQLException e) {
 System.out.println(e.getMessage());
 }
 }
 }
 public static ConnectionPool getInstance() {
 if (instance == null) {
 synchronized (ConnectionPool.class) {
 if (instance == null) {
 instance = new ConnectionPool();
 }
 }
 }
 return instance;
 }
}

CompaniesDBDAO 代码

package com.app.dbdao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import com.app.beans.Company;
import com.app.dao.CompaniesDAO;
import com.app.utils.ConnectionPool;
public class CompaniesDBDAO implements CompaniesDAO {
 private static CompaniesDBDAO instance = null;
 private ConnectionPool connectionPool = ConnectionPool.getInstance();
 private static final String QUERY_GET_ALL = "SELECT * FROM `jb project`.companies;";
 private static final String QUERY_INSERT = "INSERT INTO `jb project`.`companies` (`name`, `email`, `password`) VALUES (?, ?, ?);";
 private static final String QUERY_DELETE = "DELETE FROM `jb project`.`companies` WHERE (`id` = ?);";
 private static final String QUERY_UPDATE = "UPDATE `jb project`.`companies` SET `name` = ?, `email` = ?, `password` = ? WHERE (`id` = ?);";
 private static final String QUERY_GET_ONE = "SELECT * FROM `jb project`.companies WHERE (`id` = ?);";
 private CompaniesDBDAO() {
 }
 public static CompaniesDBDAO getInstance() {
 if (instance == null) {
 synchronized (CompaniesDBDAO.class) {
 if (instance == null) {
 instance = new CompaniesDBDAO();
 }
 }
 }
 return instance;
 }
}

问题原因分析

你遇到的核心问题是双重检查锁(DCL)单例模式的实现不完整,再加上DAO类的初始化逻辑放大了这个问题:

  1. 缺少volatile修饰符导致指令重排序
    在ConnectionPool中,private static ConnectionPool instance = null;没有使用volatile关键字修饰。JVM在实例化对象时会进行指令重排序:new ConnectionPool()的操作会被拆分为内存分配、对象初始化、引用赋值三步。如果没有volatile,JVM可能会把引用赋值的步骤提前到对象初始化之前,导致instance被标记为非null,但对象还未完全初始化。此时如果有其他线程(比如另一个DAO类初始化时)调用getInstance(),会直接返回这个未完全初始化的实例,同时可能因为后续的操作触发再次实例化,最终导致多个ConnectionPool实例被创建。

  2. DAO类的成员变量初始化时机
    你的每个DBDAO类(比如CompaniesDBDAO、CustomersDBDAO)都在实例变量声明时直接调用ConnectionPool.getInstance():private ConnectionPool connectionPool = ConnectionPool.getInstance();。当多个DAO类在短时间内被初始化时,由于ConnectionPool的单例存在指令重排序问题,每个DAO的初始化都可能触发ConnectionPool的新实例创建。

解决方案

1. 修复ConnectionPool的单例模式(关键)

给instance变量添加volatile修饰符,禁止JVM的指令重排序,确保双重检查锁的正确性:

public class ConnectionPool {
 private Stack<Connection> connections = new Stack<Connection>();
 // 添加volatile修饰符,禁止指令重排序
 private static volatile ConnectionPool instance = null;
 
 private ConnectionPool() {
 // 原有初始化逻辑不变
 }
 
 public static ConnectionPool getInstance() {
 // 原有双重检查锁逻辑不变
 if (instance == null) {
 synchronized (ConnectionPool.class) {
 if (instance == null) {
 instance = new ConnectionPool();
 }
 }
 }
 return instance;
 }
}

2. 优化DAO类中ConnectionPool的初始化(可选)

为了避免多个DAO类初始化时的潜在竞争,可以将DAO中的connectionPool改为静态变量,确保只初始化一次:

public class CompaniesDBDAO implements CompaniesDAO {
 private static CompaniesDBDAO instance = null;
 // 改为静态变量,类加载时仅初始化一次
 private static ConnectionPool connectionPool = ConnectionPool.getInstance();
 
 // ... 其他代码不变
}

或者在DAO的私有构造方法中初始化connectionPool,确保只有当DAO实例被创建时才获取连接池实例:

public class CompaniesDBDAO implements CompaniesDAO {
 private static CompaniesDBDAO instance = null;
 private ConnectionPool connectionPool;
 
 private CompaniesDBDAO() {
 // 在构造方法中初始化,延迟到DAO实例创建时
 connectionPool = ConnectionPool.getInstance();
 }
 
 // ... 其他代码不变
}

验证效果

修改后,无论你初始化多少个DAO类,ConnectionPool都只会被实例化一次,控制台只会打印10次"Creating connection #num"的信息。

内容的提问来源于stack exchange,提问作者Tal Levin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 06:32:28