我使用ExecutorService和FixedThreadPool通过JDBC执行一些SQL。然而,当我描述我的应用程序时,似乎线程计数只是提高,当然记忆也是如此。问题是它与JDBC有某种关系,因为当我在线程池的任务中删除创建语句和连接时,线程计数根本没有提高。
这是我如何将任务提交到我的线程池中:
new Thread() {
public void run() {
ExecutorService executorService = Executors.newFixedThreadPool(5);
while (!isCancelled) {
executorService.submit(RunnableTask.this);
Thread.sleep(interval);
}
executorService.shutdown(); //wait for all tasks to finish and then shutdown executor
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); //wait till shutdown finished
} catch (InterruptedException ex) {
//
}
}
};
这是我在任务中所做的事情:
try (Connection con = pool.getConnection(); PreparedStatement st = (this.isCall ? con.prepareCall(this.sql) : con.prepareStatement(this.sql))) {
st.execute();
} catch (Exception e) {
//
}
这是ConnectionPool,用于上面提到的代码(pool.getConnection(),我使用apache DBCP2:
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbcp2.BasicDataSource;
public class MySQLPool {
private BasicDataSource dataSource;
public MySQLPool setup(String driver, String uri, String user, String password, int maxConnections) throws Exception {
if (this.dataSource == null) {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName(Main.driver);
ds.setUrl(uri);
ds.setUsername(user);
ds.setPassword(password);
ds.setMaxTotal(maxConnections);
ds.setMaxWaitMillis(2000);
this.dataSource = ds;
}
return this;
}
Here is a example from profiler (imgur)
似乎线程没有正确结束,这是非常奇怪的,因为如果它是一个5个连接的固定池,ExecutorService应该用完它们吗?所以我不知道hreads是如何存在的,它们导致了相当大的内存泄漏。
问题在于创建Connection和PreparedStatement对象,因为当我将其注释掉时,线程数保持固定值。
您没有显示所有代码,例如isCancelled
。所以我们无法帮助具体。但是你的方法似乎不合时宜,所以请继续阅读。
ScheduledExecutorService
您不应该尝试管理执行程序服务的时间安排。如果你与执行者服务一起调用Thread.sleep
,你可能会做错事。
你也不应该援引new Thread
。执行程序服务的重点是让框架管理线程的细节。你工作太辛苦了。
对于任务的重复调用,请使用ScheduledExecutorService
。
有关更多信息,请参阅Oracle Tutorial,class JavaDoc和搜索Stack Overflow。该主题已经多次得到解决。
这是一个简短的例子。
使用Executors
实用程序类来创建线程池。我们只需要一个线程就可以重复调用数据库。看看你的部分代码示例,我看不出你为什么试图运行5个线程。如果要对数据库进行一系列顺序调用,则只需要一个线程。
让你的Runnable
来调用数据库。
package work.basil.example;
import java.sql.Connection;
import java.time.Instant;
public class DatabaseCaller implements Runnable
{
private Connection connection = null;
public DatabaseCaller ( Connection connection )
{
this.connection = connection;
}
@Override
public void run ()
{
// Query the database. Report results, etc.
System.out.println( "Querying the database now. " + Instant.now() );
}
}
注意:始终用run
包装try catch
方法的代码以捕获任何意外的Exception
或Error
(Throwable
)。到达执行人的任何未被捕获的投掷将导致其停止工作。该任务将不再安排进一步运行。
实例化Runnable
,并安排它重复运行。
package work.basil.example;
import java.sql.Connection;
import java.time.Instant;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class DbRepeat
{
public static void main ( String[] args )
{
DbRepeat app = new DbRepeat();
app.doIt();
}
private void doIt ()
{
System.out.println( "Starting app. " + Instant.now() );
Connection conn = null; // FIXME: Instantiate a `Connection` object here.
Runnable runnable = new DatabaseCaller( conn );
ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
long initialDelay = 0;
long delay = 5;
ScheduledFuture future = ses.scheduleWithFixedDelay( runnable , initialDelay , delay , TimeUnit.SECONDS );
// Let our demo run a few minutes.
try
{
Thread.sleep( TimeUnit.MINUTES.toMillis( 2 ) ); // Let this app run a few minutes as a demo.
} catch ( InterruptedException e )
{
System.out.println( "Somebody woke up our sleeping thread. Message # 1b296f04-3721-48de-82a8-d03b986a4b55." );
}
// Always shutdown your scheduled executor service. Otherwise its backing thread pool could continue to run, outliving the lifecycle of your app.
ses.shutdown();
System.out.println( "Ending app. " + Instant.now() );
}
}
请注意这是多么简单。
Runnable
对象中定义了一个任务。我们决不直接处理线程。我们让执行器框架处理线程的所有细节。
警告:如果使用多个线程,您仍需要使Runnable
代码成为线程安全的。执行器框架非常灵活且有用,但它并不神奇。要了解Java中的线程安全性和并发性,请每年阅读这本优秀的书籍:Brian Goetz等人的Java Concurrency in Practice。
跑步时
启动应用。 2019-03-21T19:46:09.531740Z
现在查询数据库。 2019-03-21T19:46:09.579573Z
现在查询数据库。 2019-03-21T19:46:14.585629Z
…
现在查询数据库。 2019-03-21T19:47:59.647485Z
现在查询数据库。 2019-03-21T19:48:04.650555Z
结束应用程序2019-03-21T19:48:09.579407Z
根据我的经验,许多人都夸大了对数据库连接池的需求。使用连接池有几个缺陷。我发现建立一个数据库连接并不像许多人声称的那样昂贵,特别是如果在同一台机器上本地。
所以我建议你现在跳过连接池。使用新连接时可以使代码可靠地工作。
如果由于数据库连接后来可以证明性能瓶颈,那么请考虑使用池。并验证它确实有帮助。否则你就会犯下过早优化的罪。