最近在写的数据迁移工具完成的差不多了,今天将连接池换成C3P0,发现一个问题,就是配置了多个数据源的C3P0在同时获取不同数据源的Connection时会发生死锁。
1.运行如下的代码,用JProfiler测试,会发现死锁的情况:
代码:
package com.highgo.test.c3p0deadlock;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
//加锁source个postgre的ComboPooledDataSource的getConnection用一个锁
public class Test {
public static void main(String[] args) throws InterruptedException {
ComboPooledDataSource source = new ComboPooledDataSource("source");
ComboPooledDataSource source2 = new ComboPooledDataSource("source");
ComboPooledDataSource postgres = new ComboPooledDataSource("postgres");
ComboPooledDataSource postgres2 = new ComboPooledDataSource("postgres");
new Thread(new SourceGetConn(source), "source").start();
// new Thread(new SourceGetConn(source2), "source2").start();
// Thread.sleep(1000);
new Thread(new DestGetConn(postgres), "postgres").start();
// new Thread(new DestGetConn(postgres2), "postgres2").start();
}
}
class SourceGetConn implements Runnable {
private ComboPooledDataSource source = null;
public SourceGetConn(ComboPooledDataSource source) {
this.source = source;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
source.getConnection();
System.out.println("I get a Connection! I am in " + Thread.currentThread().getName());
} catch (InterruptedException | SQLException e) {
e.printStackTrace();
}
}
}
}
class DestGetConn implements Runnable {
private ComboPooledDataSource postgres = null;
public DestGetConn(ComboPooledDataSource source) {
this.postgres = source;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
postgres.getConnection();
System.out.println("I get a Connection! I am in " + Thread.currentThread().getName());
} catch (InterruptedException | SQLException e) {
e.printStackTrace();
}
}
}
}
死锁情况:
可以看到source和postgre两个进程都被一个没有记录的对象锁住了。
2.将上边的代码的Thread.sleep注释去掉,在运行,是不会有死锁问题的,于是查看C3P0的源代码,ComboPooledDataSource@getConnection是继承自AbstractPoolBackedDataSource#getConnection,代码如下:
public Connection getConnection() throws SQLException
{
PooledConnection pc = getPoolManager().getPool().checkoutPooledConnection();
return pc.getConnection();
}
public Connection getConnection(String username, String password) throws SQLException
{
PooledConnection pc = getPoolManager().getPool(username, password).checkoutPooledConnection();
return pc.getConnection();
}
先看这个PoolManager,AbstractPoolBackedDataSource#getPoolManager方法的实现如下,是线程安全的
private synchronized C3P0PooledConnectionPoolManager getPoolManager() throws SQLException
{
if (poolManager == null)
{
ConnectionPoolDataSource cpds = assertCpds();
poolManager = new C3P0PooledConnectionPoolManager(cpds, null, null, this.getNumHelperThreads(), this.getIdentityToken(), this.getDataSourceName());
if (logger.isLoggable(MLevel.INFO))
logger.info("Initializing c3p0 pool... " + this.toString( true ) /* + "; using pool manager: " + poolManager */);
}
return poolManager;
}
从上边的代码也可以看出,一个DataSource实例,只保持一个PoolManager的引用。
再接着看