【问题标题】:Spot the concurrency issue发现并发问题
【发布时间】:2014-10-08 17:48:14
【问题描述】:

我们的一个遗留应用程序中存在数据库连接泄漏,我将其追踪到这个小宝石。从调试中,我可以看到为多个线程返回了相同的逻辑连接(不好!)。但我很难理解为什么会这样。

我们正在使用 ojdbc6 驱动程序,在带有连接池的 WebLogic 数据源上设置。

产生问题的代码

public class MyDummyDaoUtil {

    //note: this is a public field in a singleton (not a static field though...)
    public Connection conn;

    private MyDummyDaoUtil() {
    }

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

        return instance;
    }

    private DataSource getDataSource(final String dsName)
        throws NamingException {
        return ServiceLocator.getInstance().getDataSource(dsName);
    }

    public static Connection getConnection(final String source)
        throws NamingException {
        return MyDummyDaoUtil.getInstance().getDBConnection(source);
    }

    private Connection getDBConnection(final String source)
        throws NamingException {

        //the same logical connection is produced by the data source or something else happening?
        conn = getDataSource(source).getConnection(); 

        conn.setAutoCommit(false);

        return conn;
    }
}

更新修复

public class MyDummyDaoUtil {

    private MyDummyDaoUtil() {
    }

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

        return instance;
    }

    private DataSource getDataSource(final String dsName)
        throws NamingException {
        return ServiceLocator.getInstance().getDataSource(dsName);
    }

    public static Connection getConnection(final String source)
        throws NamingException {
        return MyDummyDaoUtil.getInstance().getDBConnection(source);
    }

    private Connection getDBConnection(final String source)
        throws NamingException {

        Connection conn = getDataSource(source).getConnection();
        conn.setAutoCommit(false);

        return conn;
    }
}

修复摘要

  1. 实例的延迟初始化不正确
  2. Connection 应该是方法中的局部变量,而不是单例类

【问题讨论】:

  • 你说 MyDummyDaoUtil 是一个单例。如果这在多个线程之间共享,那么将有机会请求两个连接来改变引用,并且从两次调用 getDBConnection 返回相同的引用(最后一个)。未返回的连接永远无法关闭。这段代码最终不会耗尽可用的连接吗?
  • @Yoztastic 连接可能在垃圾回收时被终结器击落,从而隐藏了这个问题。
  • @Yoztastic - 是的,这是最初的症状。我们的连接已耗尽,我们不得不打开非活动超时来帮助回收那些泄露的连接,同时我们追踪问题。据我所知,代码从第一天开始就以这种方式实现(这很可怕......)目前还不清楚这个问题是如何在我们的旧系统上出现的。我的猜测是连接以某种方式被回收,我们没有意识到这个问题的存在。

标签: java multithreading jdbc database-connection connection-pooling


【解决方案1】:

您正在使用单例模式,并且在多线程环境中使用时必须以同步方式处理对象实例化。

尝试任何一个选项:

  • 使用getInstance()同步方法

    public static synchronized MyDummyDaoUtil getInstance() {
        if (instance == null) {
            instance = new MyDummyDaoUtil();
        } 
        return instance;
    }
    
  • 实例化它急切地

    private static MyDummyDaoUtil instance = new MyDummyDaoUtil();
    public static MyDummyDaoUtil getInstance() {
        return instance;
    }
    
  • 使用双重检查锁定机制

    public static MyDummyDaoUtil getInstance() {
    
        if (instance == null) {
            synchronized(MyDummyDaoUtil.class){
                 if (instance == null) {
                     instance = new MyDummyDaoUtil();
                 }
             }
        }
        return instance;
    }
    

注意:不要忘记关闭连接完成。

Read more...

【讨论】:

    【解决方案2】:

    假设您要问的是“为什么更改此代码可以解决 getDBConnection 在多个线程上返回相同对象的问题”...

    您正在使用可变状态 (MyDummyDaoUtil.conn)。考虑以下场景 - 两个线程(A 和 B)同时调用您的原始函数:

    private Connection getDBConnection(final String source)
        throws NamingException {
        conn = getDataSource(source).getConnection(); //line 1
        conn.setAutoCommit(false);                    //line 2
        return conn;                                  //line 3
    }
    

    这里有很多可能的序列,但这里有一个有问题的示例:

    • 线程 A 执行第 1 行。您的数据源返回一个新连接(我们称之为connectionA),并且MyDummyDaoUtil.conn 设置为connectionA
    • 线程 B 执行第 1 行。您的数据源返回一个新连接(我们称之为connectionB),并且MyDummyDaoUtil.conn 设置为connectionB
    • 线程 A 执行第 2 行。conn 现在是 connectionB,因此这会导致在 connectionB 上将自动提交设置为 false。
    • 线程 A 执行第 3 行,返回 connectionB(这是从另一个线程创建的)。
    • 线程 B 执行第 2 行,在 connectionB 上将自动提交设置为 false(这是一个无操作,因为线程 A 已经意外地这样做了)
    • 线程 B 执行第 3 行,返回 connectionB

    问题在于MyDummyDaoUtil.conn,因为它是一个单例成员变量,所以在两个线程中都引用了同一个变量。在您的第二个示例中,有一个局部变量这一事实意味着该函数的每次调用都有一个单独的变量,因此您不会得到调用之间的交叉污染。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-31
      • 2013-07-26
      • 2013-12-01
      相关资源
      最近更新 更多