最近在写的数据迁移工具完成的差不多了,今天将连接池换成C3P0,发现一个问题,就是配置了多个数据源的C3P0在同时获取不同数据源的Connection时会发生死锁。 1.运行如下的代码,用JProfiler测试,会发现死锁的情况: 代码: package com.highgo.test.c3p0dea
最近在写的数据迁移工具完成的差不多了,今天将连接池换成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的引用。
再接着看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); } }返回一个C3P0PooledConnection 实例;C3P0PooledConnection 这个类里的方法都是线程安全的。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(); } }从上边的源码分析可以看出,一个ComboPooledDataSource实例的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(); } } } }将一个ComboPooledDataSource实例,传给两个线程分别getConnection,getConnection的过程没有加锁的情况下是可以运行的,完全没有问题。
3.经过测试发现同一个数据源的两个ComboPooledDataSource实例,getConnection方法不加锁的情况下,也是没有问题的。
稍微总结一下:
C3P0在一个ComboPooledDataSource实例的getConnection方法是线程安全的
C3P0在一个数据源的多个ComboPooledDataSource实例的getConnection方法也是线程安全的
C3P0在多个数据源的多个ComboPooledDataSource不同时调用getConnection的情况下,不会发生死锁(基于概率,若干时间之后,肯定会发生死锁)
C3P0在多个数据源的多个ComboPooledDataSource实例的getConnection方法同时(相邻的两行代码)调用时,会发生死锁现象,如1中所述
4.总结:
属于不同数据源的多个ComboPooledDataSource实例的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"; } } } } }

MySQL 기능은 데이터 처리 및 계산에 사용될 수 있습니다. 1. 기본 사용에는 문자열 처리, 날짜 계산 및 수학 연산이 포함됩니다. 2. 고급 사용에는 복잡한 작업을 구현하기 위해 여러 기능을 결합하는 것이 포함됩니다. 3. 성능 최적화를 위해서는 WHERE 절에서 기능 사용 및 GroupBy 및 임시 테이블 사용을 피해야합니다.

MySQL에 데이터 삽입을위한 효율적인 방법은 다음과 같습니다. 1. InsertInto 사용 ... 값 구문 사용 ... 값 구문, 2. 트랜잭션 처리 사용, 3. 트랜잭션 처리 사용, 4. 배치 크기 조정, 5. 인덱스 비활성화, 6. Insertignore 또는 Insert ... ondupliceKeyUpdate를 사용하여 데이터베이스 작동 효율성을 크게 향상시킬 수 있습니다.

MySQL에서는 altertabletable_nameaddcolumnnew_columnvarchar (255) 이후에 필드를 추가하여 altertabletable_namedropcolumncolumn_to_drop을 사용하여 필드를 삭제합니다. 필드를 추가 할 때는 쿼리 성능 및 데이터 구조를 최적화하기위한 위치를 지정해야합니다. 필드를 삭제하기 전에 작업이 돌이킬 수 없는지 확인해야합니다. 온라인 DDL, 백업 데이터, 테스트 환경 및 저하 기간을 사용하여 테이블 구조 수정은 성능 최적화 및 모범 사례입니다.

설명 명령을 사용하여 MySQL 쿼리의 실행 계획을 분석하십시오. 1. 설명 명령은 성능 병목 현상을 찾는 데 도움이되는 쿼리의 실행 계획을 표시합니다. 2. 실행 계획에는 id, select_type, 테이블, type, cly_keys, key, key_len, ref, 행 및 추가 필드와 같은 필드가 포함됩니다. 3. 실행 계획에 따르면 인덱스를 추가하고 전체 테이블 스캔을 피하고 조인 작업 최적화 및 오버레이 인덱스를 사용하여 쿼리를 최적화 할 수 있습니다.

하위 쿼리는 MySQL 쿼리의 효율성을 향상시킬 수 있습니다. 1) 서브 쿼리는 데이터 필터링 및 집계 된 값을 계산하는 것과 같은 복잡한 쿼리 로직을 단순화합니다. 2) MySQL Optimizer는 하위 쿼리를 전환하여 작업에 참여하여 성능을 향상시킬 수 있습니다. 3) IN 대신에 사용하면 여러 행이 반환 오류를 피할 수 있습니다. 4) 최적화 전략에는 관련 하위 쿼리를 피하고, 사용 사용, 색인 최적화 및 하위 퀘스트 중첩을 피하는 것이 포함됩니다.

MySQL에서 문자 세트 및 콜라주를 구성하는 방법은 다음과 같습니다. 1. 서버 수준에서 문자 세트 및 콜라주 설정 : setNames'Utf8 '; setcharactersetutf8; setCollation_connection = 'utf8_general_ci'; 2. 특정 문자 세트 및 콜라주를 사용하는 데이터베이스를 만듭니다. createAbaseexample_DBCHARACTERSETUTF8COLLATEUTF8_GENERAL_CI; 3. 테이블을 만들 때 문자 세트 및 콜라주를 지정하십시오 : CreateTableAmplipt_table (idint

MySQL을 안전하고 철저하게 제거하고 모든 잔차 파일을 정리하려면 다음 단계를 따르십시오. 1. MySQL 서비스 중지; 2. MySQL 패키지 제거; 3. 구성 파일 및 데이터 디렉토리를 정리하십시오. 4. 제거가 철저한 지 확인하십시오.

MySQL에서 데이터베이스를 바꾸려면 간접적 인 방법이 필요합니다. 단계는 다음과 같습니다. 1. 새 데이터베이스를 만듭니다. 2. mysqldump를 사용하여 이전 데이터베이스를 내보내십시오. 3. 데이터를 새 데이터베이스로 가져옵니다. 4. 이전 데이터베이스를 삭제합니다.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

SublimeText3 Linux 새 버전
SublimeText3 Linux 최신 버전

SecList
SecLists는 최고의 보안 테스터의 동반자입니다. 보안 평가 시 자주 사용되는 다양한 유형의 목록을 한 곳에 모아 놓은 것입니다. SecLists는 보안 테스터에게 필요할 수 있는 모든 목록을 편리하게 제공하여 보안 테스트를 더욱 효율적이고 생산적으로 만드는 데 도움이 됩니다. 목록 유형에는 사용자 이름, 비밀번호, URL, 퍼징 페이로드, 민감한 데이터 패턴, 웹 셸 등이 포함됩니다. 테스터는 이 저장소를 새로운 테스트 시스템으로 간단히 가져올 수 있으며 필요한 모든 유형의 목록에 액세스할 수 있습니다.

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

VSCode Windows 64비트 다운로드
Microsoft에서 출시한 강력한 무료 IDE 편집기

PhpStorm 맥 버전
최신(2018.2.1) 전문 PHP 통합 개발 도구
