发现并发问题

时间:2014-10-08 17:48:15

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

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

我们正在使用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应该是方法中的局部变量,而不是单例类

2 个答案:

答案 0 :(得分:0)

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

尝试任何一个选项:

  • 使用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;
    }
    

注意:请勿忘记关闭Connection。

Read more...

答案 1 :(得分:0)

假设您要问的是“为什么更改此代码会修复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,因为它是一个单例成员变量,所以在两个线程中引用相同的变量。在你的第二个例子中,有一个局部变量的事实意味着每次调用函数都有一个单独的变量,因此你不会在调用之间产生交叉污染。