JDBC连接池问题 - 获取连接时出现死锁

时间:2012-04-29 13:47:08

标签: java multithreading jdbc connection-pooling

我在Tomcat中使用JDBC连接池。要检索连接,我已经定义了一个连接工厂,如下所示:

public class ConnectionManager {

   // reference to the ConnectionManager
   private static ConnectionManager instance = null;
   // Connection to MySQL database
   private Connection connect = null;

   private static DataSource ds = null;
   // Logger
   public static final Logger logger = Logger
         .getLogger(ConnectionManager.class);

   static {

      try {
         Context initCtx = new InitialContext();
         Context envCtx = (Context) initCtx.lookup("java:comp/env");
         ds = (DataSource) envCtx.lookup("jdbc/ConnectionManager");
      } catch (NamingException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
   }

   /**
    * Private Constructor .. since its a singleton
    * 
    */
   private ConnectionManager() {

   }

   public static ConnectionManager getInstance() {
      if (instance == null) {
         instance = new ConnectionManager();
      }
      return instance;
   }

   public Connection getDbConnection() {
      Connection conn = null;

      try {
         synchronized (DataSource.class) {

            conn = ds.getConnection();
         }
      } catch (SQLException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }

      return conn;
   }

   public void closeDbConnection() throws SQLException {
      conn.close();
   }
}

现在我发现我的代码总是卡在conn = ds.getConnection();行。请让我知道我做错了什么。从我的DAO方法中,我使用以下内容来获取连接:conn = ds.getConnection();

显然它是一个多线程问题。我该怎么办?

4 个答案:

答案 0 :(得分:3)

您的大部分课程似乎都围绕检索JNDI数据源并使用它来创建连接。不一定是个糟糕的主意,但在这种情况下,您已经在程序中引入了一些错误,而且复杂性很高。

首先,你的单身人士不是单身人士。您没有同步getInstance方法,因此可以在多个线程上同时调用此方法。 Java(不幸的是)用于实现单例的最佳方法是通过枚举:

public enum ConnectionManager {
    INSTANCE;
}

您的第二个重要问题是您正在同步一个您不明确控制的课程。没有什么能阻止第三方JAR甚至您自己的应用程序中的其他类在DataSource类上进行同步,这使其成为死锁问题的普遍目标。我会从类中取出所有多余的方法并删除同步块:

public enum ConnectionManager {
    INSTANCE;

    private DataSource ds = null;

    ConnectionManager() {
      try {
         final Context initCtx = new InitialContext();
         final Context envCtx = (Context) initCtx.lookup("java:comp/env");
         ds = (DataSource) envCtx.lookup("jdbc/ConnectionManager");
      } catch (NamingException e) {
         e.printStackTrace();
      }
    }

   public Connection getConnection() throws SQLException {
      if(ds == null) return null;

      return ds.getConnection();
   }
}

现在,根据我的经验,大多数数据源实现都是线程安全的,因此上述代码应该在大多数情况下都能正常工作。但是,我们不应该依赖我们无法控制的实现,所以让我们为代码添加安全同步,如下所示:

public enum ConnectionManager {
    INSTANCE;

    private DataSource ds = null;
    private Lock connectionLock = new ReentrantLock();

    ConnectionManager() {
      try {
         final Context initCtx = new InitialContext();
         final Context envCtx = (Context) initCtx.lookup("java:comp/env");
         ds = (DataSource) envCtx.lookup("jdbc/ConnectionManager");
      } catch (NamingException e) {
         e.printStackTrace();
      }
    }

   public Connection getConnection() throws SQLException {
      if(ds == null) return null;

      Connection conn = null;
      connectionLock.lock();
      try {
          conn = ds.getConnection();
      } finally {
          connectionLock.unlock();
      }

      return conn;
   }
}

您不必添加包装器方法来关闭连接,这是调用代码的责任。祝你好运。

答案 1 :(得分:0)

@arya,好像你遇到了连接泄漏的问题,并且由于这个问题,池已经用尽,代码只是等待它获得新的连接,要分析问题,请使用任何数据库监控工具,或手动尝试跟踪泄漏(代码中您已经消耗连接但在使用后忘记将其返回池中的点)。

答案 2 :(得分:0)

嗯,我会说先试用你的dataSource是否正常使用测试源 我建议查看适用于Apache Tomcat 6.0Apache Tomcat 7.0的Apache Tomcat JNDI数据资源的方法。

仔细查看说明并分析代码中出现的问题,然后针对特定问题更新问题。

答案 3 :(得分:0)

该代码几乎可以保证在多线程系统中导致连接泄漏。 closeDbConnection()仅关闭从池中借用的最后一个连接 - 因此,如果10个线程调用了getDbConnection(),那么closeDbConnection()之后,只有1个连接被关闭,9个连接仍处于活动状态。重复几次并且池耗尽(除非在finalize()中清除连接,但情况可能并非如此)。我会摆脱整个班级,或者将其重新设计为仅作为数据源定位器。