C 3 P 0マルチデータソースのデッドロック問題


最近书いてあるデータ移行ツールの完成の差は多くなくて、今日接続プールをC 3 P 0に変えて、1つの问题を発见して、1つの问题を配置したC 3 P 0は同时に异なるデータソースの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();
			}
		}
	}

}

デッドロック:
ソースとpostgreの両方のプロセスが記録されていないオブジェクトにロックされていることがわかります.
2.上のコードのThread.sleepコメントを削除し、実行中はデッドロックの問題はありませんので、C 3 P 0のソースコードを見て、ComboPooledDataSource@getConnectionAbstractPoolBackedDataSourcegetConnectionから継承されます.コードは次のとおりです.
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,A b s t r actPoolBackedDataSource#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;	    
    }
上記のコードからも分かるように、PoolManagerの参照は1つのDataSourceインスタンスのみが保持されます.
次にgetPoolメソッドを見ても、スレッドは安全です.
public synchronized C3P0PooledConnectionPool getPool(String username, String password, boolean create) throws SQLException
    {
        if (create)
            return getPool( username, password );
        else
        {
            DbAuth checkAuth = new DbAuth( username, password );
            C3P0PooledConnectionPool out = (C3P0PooledConnectionPool) authsToPools.get(checkAuth);
            if (out == null)
                throw new SQLException("No pool has been initialized for databse user '" + username + "' with the specified password.");
            else
                return out;
        }
    }

もう一度見る
C3P0PooledConnectionPool#checkoutPooledConnection();
public PooledConnection checkoutPooledConnection() throws SQLException
    { 
        //System.err.println(this + " -- CHECKOUT");
        try 
	    { 
		PooledConnection pc = (PooledConnection) this.checkoutAndMarkConnectionInUse(); 
		pc.addConnectionEventListener( cl );
		return pc;
	    }
        catch (TimeoutException e)
        { throw SqlUtils.toSQLException("An attempt by a client to checkout a Connection has timed out.", e); }
        catch (CannotAcquireResourceException e)
        { throw SqlUtils.toSQLException("Connections could not be acquired from the underlying database!", "08001", e); }
        catch (Exception e)
        { throw SqlUtils.toSQLException(e); }
    }

1つ戻る
C3P0PooledConnection
インスタンス;
C 3 P 0 PooledConnectionというクラスのメソッドはスレッドが安全です.ComboPooledDataSource@getConnection最後の駅は
C3P0PooledConnection
#getConnection;次のようになります.
 public synchronized Connection getConnection()
	throws SQLException
    { 
	if ( exposedProxy != null)
	    {
		//DEBUG
		//System.err.println("[DOUBLE_GET_TESTER] -- double getting a Connection from " + this );
		//new Exception("[DOUBLE_GET_TESTER] -- Double-Get Stack Trace").printStackTrace();
		//origGet.printStackTrace();

// 		System.err.println("c3p0 -- Uh oh... getConnection() was called on a PooledConnection when " +
// 				   "it had already provided a client with a Connection that has not yet been " +
// 				   "closed. This probably indicates a bug in the connection pool!!!");

		logger.warning("c3p0 -- Uh oh... getConnection() was called on a PooledConnection when " +
			       "it had already provided a client with a Connection that has not yet been " +
			       "closed. This probably indicates a bug in the connection pool!!!");

		return exposedProxy;
	    }
	else
	    { return getCreateNewConnection(); }
    }

上のソース分析から、CombooledDataSourceの例がわかります.ComboPooledDataSource@getConnectionスレッドは安全で、安心して呼び出すことができます.テストして、最初のコードを少し修正して、以下のようにします.
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(source), "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();
			}
		}
	}

}

1つの
CommoPooledDataSourceインスタンスは、2つのスレッドにそれぞれgetConnectionが渡され、getConnectionのプロセスにロックがかかっていない場合は実行可能で、全く問題ありません.
3.同じデータソースの2つをテストで発見
CombooledDataSourceインスタンス、getConnectionメソッドがロックされていない場合でも問題ありません.
ちょっとまとめてみます.
C 3 P 0 CombooledDataSourceインスタンスのgetConnectionメソッドはスレッドが安全です
C 3 P 0は、1つのデータソースの複数のCombooledDataSourceインスタンスのgetConnectionメソッドでもスレッドが安全です
C 3 P 0複数のデータソースの複数のCombooledDataSourceがgetConnectionを同時に呼び出さない場合、デッドロックは発生しません(確率に基づいて、数時間後には必ずデッドロックが発生します)
C 3 P 0は、複数のデータソースの複数のCombooledDataSourceインスタンスのgetConnectionメソッドが同時に(隣接する2行のコード)呼び出されると、1で説明したようにデッドロック現象が発生する
4.まとめ:
異なるデータ・ソースに属する複数のCombooledDataSourceインスタンスのgetConnectionメソッド呼び出し反発
テストコードは次のとおりです.
package com.highgo.test.c3p0deadlock;

import java.sql.SQLException;
import java.util.concurrent.locks.ReentrantLock;

import com.mchange.v2.c3p0.ComboPooledDataSource;

// source postgre ComboPooledDataSource getConnection 
public class Test2 {

	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");
		ReentrantLock lock = new ReentrantLock();
		new Thread(new SourceGetConn2(source, lock), "source").start();
		new Thread(new SourceGetConn2(source2, lock), "source2").start();
		Thread.sleep(1000);
		new Thread(new DestGetConn2(postgres, lock), "postgres").start();
		new Thread(new DestGetConn2(postgres2, lock), "postgres2").start();
	}

}

class SourceGetConn2 implements Runnable {

	private ComboPooledDataSource source = null;
	private ReentrantLock lock;

	public SourceGetConn2(ComboPooledDataSource source, ReentrantLock lock) {
		this.source = source;
		this.lock = lock;
	}

	@Override
	public void run() {
		while (true) {
			try {
				Thread.sleep(1000);
				lock.lock();
				source.getConnection();
				lock.unlock();
				System.out.println("I get a Connection! I am in " + Thread.currentThread().getName());
			} catch (InterruptedException | SQLException e) {
				e.printStackTrace();
			}
		}
	}

}

class DestGetConn2 implements Runnable {

	private ComboPooledDataSource postgres = null;
	private ReentrantLock lock;

	public DestGetConn2(ComboPooledDataSource source, ReentrantLock lock) {
		this.postgres = source;
		this.lock = lock;
	}

	@Override
	public void run() {
		while (true) {
			try {
				Thread.sleep(1000);
				lock.lock();
				postgres.getConnection();
				lock.unlock();
				System.out.println("I get a Connection! I am in " + Thread.currentThread().getName());
			} catch (InterruptedException | SQLException e) {
				e.printStackTrace();
			}
		}
	}

}

5.最後に、効率的なツールクラスをまとめる
package com.highgo.hgdbadmin.myutil;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0Util {

	public static String SOURCE = "source";
	public static String POSTGRES = "postgres";

	private ComboPooledDataSource source = null;
	private ComboPooledDataSource postgres = null;

	private static C3P0Util instance = null;

	private C3P0Util() {
		source = new ComboPooledDataSource("source");
		postgres = new ComboPooledDataSource("postgres");
	}

	public static final synchronized C3P0Util getInstance() {
		if (instance == null) {
			instance = new C3P0Util();
		}
		return instance;
	}

	public synchronized Connection getConnection(String dataSource) throws SQLException {
		if ("source".equals(dataSource)) {
			return source.getConnection();
		} else if ("postgres".equals(dataSource)) {
			return postgres.getConnection();
		}
		return null;
	}

	public synchronized void close(Connection conn) {
		try {
			if (conn != null) {
				conn.close();
				conn = null;
			}
		} catch (SQLException e) {
		}
	}

	public synchronized void close(Statement stat) {
		try {
			if (stat != null) {
				stat.close();
				stat = null;
			}
		} catch (SQLException e) {
		}
	}

	public synchronized void close(ResultSet rest) {
		try {
			if (rest != null) {
				rest.close();
				rest = null;
			}
		} catch (SQLException e) {
		}
	}

	public static void main(String[] args) {
		new Thread(new TestThread(), "test").start();
	}

	private static class TestThread implements Runnable {

		private String dataSource = "source";

		@Override
		public void run() {
			while (true) {
				try {
					Connection conn = C3P0Util.getInstance().getConnection("");
					System.out.println("hello,this is " + dataSource);
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if ("source".equals(dataSource)) {
					dataSource = "postgres";
				} else {
					dataSource = "source";
				}
			}

		}

	}
}