Java 找出并发性问题
我们的一个遗留应用程序中有一个数据库连接泄漏,我跟踪到了这个小gem。通过调试,我可以看到多个线程返回相同的逻辑连接(不好!)。但我很难理解为什么会这样 我们正在使用ojdbc6驱动程序,该驱动程序安装在带有连接池的WebLogic数据源上 产生问题的代码Java 找出并发性问题,java,multithreading,jdbc,database-connection,connection-pooling,Java,Multithreading,Jdbc,Database Connection,Connection Pooling,我们的一个遗留应用程序中有一个数据库连接泄漏,我跟踪到了这个小gem。通过调试,我可以看到多个线程返回相同的逻辑连接(不好!)。但我很难理解为什么会这样 我们正在使用ojdbc6驱动程序,该驱动程序安装在带有连接池的WebLogic数据源上 产生问题的代码 public class MyDummyDaoUtil { //note: this is a public field in a singleton (not a static field though...) publi
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;
}
}
修复摘要
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;
}
}
您使用的是单例模式,在多线程环境中使用时,必须以同步方式处理对象实例化 请尝试以下任一选项:
- 使用
synchronized方法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; }
假设您要问的是“为什么更改此代码可以解决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
上将自动提交设置为falseconnectionB
- 线程A执行第3行,返回
(从另一个线程创建的连接)连接B
- 线程B执行第2行,在
上将auto commit设置为false(这是一个no-op,因为线程a在意外情况下已经这样做了)connectionB
- 线程B执行第3行,返回
connectionB
问题在于
MyDummyDaoUtil.conn
,因为它是一个单例成员变量,所以在两个线程中引用相同的变量。在第二个示例中,有一个局部变量这一事实意味着函数的每次调用都有一个单独的变量,因此不会出现调用之间的交叉污染。您说MyDummyDaoUtil是一个单例。如果这是跨多个线程共享的,那么将有机会请求两个连接,修改引用和从对getDBConnection的两个调用返回的相同引用(最后一个)。无法关闭未返回的连接。这段代码没有耗尽可用的连接吗?@Yoztastic这些连接可能在垃圾收集时被终结器击落,从而隐藏了这个问题。@Yoztastic-是的,这是最初的症状。我们的连接已耗尽,我们必须打开非活动超时,以便在跟踪问题时帮助回收泄漏的连接。据我所知,代码从第一天起就以这种方式实现了(这很可怕……),不清楚这个问题是如何在我们的旧系统上没有表现出来的。我的猜测是这些连接不知何故被回收了,而我们没有意识到这个问题的存在。