英文:
java dbcp2 multithreaded connection access
问题
我对在Tomcat上使用连接池非常熟悉,并且多年来一直在使用它,没有任何问题。然而,目前我正在开发一个需要同时运行多个线程以提高性能的主方法应用程序,而这些线程都需要访问同一个数据库。我已经将我的代码调整得可以在剥离数据库代码的情况下工作,并且仅为测试目的使用数组(例如,多线程可以正常工作)。然而,一旦我重新添加数据库连接,第一个线程就会锁定,其他线程根本不会运行。我已经尝试过c3p0和dbcp2;目前正在使用dbcp2。谢谢!有大量的文档可供参考,但很少有代码示例似乎适用于我的用例。以下是一个示例应用程序:
import java.sql.*;
import org.apache.commons.dbcp2.BasicDataSource;
public class SandboxApp {
private static BasicDataSource dataSource;
public static BasicDataSource getDataSource() {
if (dataSource == null) {
BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/my-db");
ds.setUsername("root");
ds.setPassword("");
ds.setDriverClassName("org.mariadb.jdbc.Driver");
ds.setInitialSize(3);
ds.setMaxTotal(25);
ds.setMinIdle(0);
ds.setMaxIdle(8);
ds.setMaxOpenPreparedStatements(100);
dataSource = ds;
}
return dataSource;
}
public static void main(String [] args) throws Exception{
for(int i=0; i<11; i++){//spawn 11 threads & get each thread to process 600k sql rows at the same time
new Thread("" + (i*600000)){
public void run(){
System.out.println("Thread: " + getName() + " running");//prints correctly for all threads
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = SandboxApp.getDataSource().getConnection();
pstmt = con.prepareStatement("select something from some_table limit "+getName()+",600000");
rs=pstmt.executeQuery();
while(rs.next()){
System.out.println("Doing stuff for thread "+getName());//this only prints for getName() == 0
//give the other threads a turn...
try {
Thread.sleep(10);
}
catch(InterruptedException ex) {
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {pstmt.close();} catch (SQLException e) {}
try { con.close(); } catch(Exception e) {}
try { rs.close(); } catch(Exception e) {}
}
}
}.start();
}
}
}
请注意,我已经省略了引入包和类的部分。如果您有进一步的问题,请随时问我。
英文:
I'm pretty familiar using connection pooling on tomcat & have used it for years without problem. However at the moment I'm working on a main method application that needs to run simultaneous threads for performance reasons, and those threads each need to access the same database. I've gotten my code to work if I strip out database code altogether & just use arrays for test purposes (e.g. multithreading works) however as soon as I add back in database connections, the first thread takes the lock and the other threads don't run at all. Have played with c3p0, and dbcp2; currently working with dbcp2. Thanks! There's tons of documentation out there, but not many code samples that seem specific to my use case. Here's a sample app:
import java.sql.*;
import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.dbcp2.PoolingDataSource;
import org.apache.commons.dbcp2.PoolingDriver;
import org.apache.commons.dbcp2.Utils;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
public class SandboxApp {
private static BasicDataSource dataSource;
public static BasicDataSource getDataSource() {
if (dataSource == null) {
BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/my-db");
ds.setUsername("root");
ds.setPassword("");
ds.setDriverClassName("org.mariadb.jdbc.Driver");
ds.setInitialSize(3);
ds.setMaxTotal(25);
ds.setMinIdle(0);
ds.setMaxIdle(8);
ds.setMaxOpenPreparedStatements(100);
dataSource = ds;
}
return dataSource;
}
public static void main(String [] args) throws Exception{
for(int i=0; i<11; i++){//spawn 11 threads & get each thread to process 600k sql rows at the same time
new Thread("" + (i*600000)){
public void run(){
System.out.println("Thread: " + getName() + " running");//prints correctly for all threads
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = SandboxApp.getDataSource().getConnection();
pstmt = con.prepareStatement("select something from some_table limit "+getName()+",600000");
rs=pstmt.executeQuery();
while(rs.next()){
System.out.println("Doing stuff for thread "+getName());//this only prints for getName() == 0
//give the other threads a turn...
try {
Thread.sleep(10);
}
catch(InterruptedException ex) {
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {pstmt.close();} catch (SQLException e) {}
try { con.close(); } catch(Exception e) {}
try { rs.close(); } catch(Exception e) {}
}
}
}.start();
}
}
}
答案1
得分: 0
@user207421是对的,getDataSource()方法应该是同步的,当然我之前已经尝试过这个;然而这仍然没有解决线程"0"不让其他线程轮流运行的问题。
我从代码中剥离了所有其他库等内容,直到它能够工作,然后开始逐步构建它,找出破坏点。似乎主要的决定性因素是ResultSet的大小。我尝试在各个地方添加额外的线程休眠时间,然而唯一有效的方法是将查询拆分为请求较小的ResultSets。
有600k个结果集,只有1个线程会运行,有1k个结果集,4个线程会运行。只有包含100行的ResultSets,所有11个线程都会运行。请注意,我在一个16个CPU系统上进行测试,JVM分配了8GB的内存(aws m5.4xlarge),因此硬件资源不应该是一个 contributing factor。所以我想我只能将代码分成较小的块。
当我最初研究这个问题时,我对缺乏针对这个特定问题的特定代码示例感到惊讶(与ResultSet大小和线程数量无关),因此我只是在这里发布了最终对我有效的代码示例,以供参考:
import java.sql.*;
import org.apache.commons.dbcp2.BasicDataSource;
public class SandboxApp {
private static BasicDataSource dataSource;
public static synchronized BasicDataSource getDataSource() {
if (dataSource == null) {
BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/my-db");
ds.setUsername("root");
ds.setPassword("");
ds.setDriverClassName("org.mariadb.jdbc.Driver");
ds.setInitialSize(3);
ds.setMaxTotal(25);
ds.setMinIdle(0);
ds.setMaxIdle(8);
ds.setMaxOpenPreparedStatements(100);
dataSource = ds;
}
return dataSource;
}
public static void main(String [] args) throws Exception{
for(int i=0; i<11; i++){//spawn 11 threads & get each thread to process 100 sql rows at the same time
new Thread("" + (i*100)){
public void run(){
System.out.println("Thread: " + getName() + " running");
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = SandboxApp.getDataSource().getConnection();
pstmt = con.prepareStatement("select something from some_table limit "+getName()+",100");
rs=pstmt.executeQuery();
while(rs.next()){
System.out.println("Doing stuff for thread "+getName());//With smaller ResultSet, this works fine for all 11 threads
//give the other threads a turn...
try {
Thread.sleep(10);
}
catch(InterruptedException ex) {
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {pstmt.close();} catch (SQLException e) {}
try { con.close(); } catch(Exception e) {}
try { rs.close(); } catch(Exception e) {}
}
}
}.start();
}
}
}
英文:
@user207421 was right, that the getDataSource() method should be synchronized & of course I'd already tried this; however this still didn't solve for my problem of thread "0" not letting the other threads take a turn.
I stripped back everything from my code, all other libraries etc.. until I got it to work, and then started building it back up again to find the breaking point. It seems that the main determining factor is the size of the ResultSet. I tried adding in extra thread.sleep time in various places however the only thing that has worked is breaking down the queries to request smaller ResultSets.
600k result sets, only 1 thread will run, 1k ResultSets and 4 threads will run. With ResultSets containing only 100 rows, all 11 threads will run. Note, I was testing this on a 16 CPU system with 8GB of memory allocated to the JVM (aws m5.4xlarge), so hardware resources shouldn't have been a contributing factor. So I guess I'll just have to break my code into smaller chunks.
When I was initially looking into this I was surprised as to the lack of a specific code samples for this specific problem (irrespective of ResultSet size & number of threads), so I'm just posting here what finally worked for me for the sake of a complete code sample:
import java.sql.*;
import org.apache.commons.dbcp2.BasicDataSource;
public class SandboxApp {
private static BasicDataSource dataSource;
public static synchronized BasicDataSource getDataSource() {
if (dataSource == null) {
BasicDataSource ds = new BasicDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/my-db");
ds.setUsername("root");
ds.setPassword("");
ds.setDriverClassName("org.mariadb.jdbc.Driver");
ds.setInitialSize(3);
ds.setMaxTotal(25);
ds.setMinIdle(0);
ds.setMaxIdle(8);
ds.setMaxOpenPreparedStatements(100);
dataSource = ds;
}
return dataSource;
}
public static void main(String [] args) throws Exception{
for(int i=0; i<11; i++){//spawn 11 threads & get each thread to process 100 sql rows at the same time
new Thread("" + (i*100)){
public void run(){
System.out.println("Thread: " + getName() + " running");
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = SandboxApp.getDataSource().getConnection();
pstmt = con.prepareStatement("select something from some_table limit "+getName()+",100");
rs=pstmt.executeQuery();
while(rs.next()){
System.out.println("Doing stuff for thread "+getName());//With smaller ResultSet, this works fine for all 11 threads
//give the other threads a turn...
try {
Thread.sleep(10);
}
catch(InterruptedException ex) {
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {pstmt.close();} catch (SQLException e) {}
try { con.close(); } catch(Exception e) {}
try { rs.close(); } catch(Exception e) {}
}
}
}.start();
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论