简体   繁体   English

与 JDBC 一起使用时,Java Executor 服务不会关闭线程

[英]Java Executor service is not closing threads when used with JDBC

I use ExecutorService with FixedThreadPool to execute some SQL via JDBC.我使用 ExecutorService 和 FixedThreadPool 通过 JDBC 执行一些 SQL。 However when I profile my app it seems that thread count is just raising and so does the memory of course.但是,当我分析我的应用程序时,似乎线程数正在增加,当然内存也是如此。 The problem is that is is somehow related to JDBC because when I remove creating statements and connections inside my task for the thread pool, the thread count is not raising at all.问题是这与 JDBC 有某种关系,因为当我在线程池的任务中删除创建语句和连接时,线程数根本没有增加。

Here is how I sumbit tasks into my thread pool:以下是我将任务汇总到线程池中的方式:

       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) {
                    //
                }
            }
        };

Here is what I do in the task:这是我在任务中所做的:

    try (Connection con = pool.getConnection(); PreparedStatement st = (this.isCall ? con.prepareCall(this.sql) : con.prepareStatement(this.sql))) {
        st.execute();
    } catch (Exception e) {
        //
    }

Here is the ConnectionPool, that is used in above mentioned code (pool.getConnection(), I use apache DBCP2:这是上面提到的代码中使用的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)这是分析器 (imgur) 的示例

It seems that the threads are not ended properly, which is very weird because the ExecutorService should run out of them if it is a fixed pool of 5 connections right ?似乎线程没有正确结束,这很奇怪,因为如果 ExecutorService 是 5 个连接的固定池,那么 ExecutorService 应该用完它们,对吗? So I have no idea how the hreads are still there, they are causing quite large memory leak.所以我不知道 hreads 是如何仍然存在的,它们导致了相当大的内存泄漏。

The problem is in creating Connection and PreparedStatement objects because when I comment it out, the number of threads stays at a fixed value.问题在于创建 Connection 和 PreparedStatement 对象,因为当我将其注释掉时,线程数保持固定值。

You do not show all your code such as isCancelled .您不会显示所有代码,例如isCancelled So we can't help specifically.所以我们无法具体提供帮助。 But your approach seems to be off-kilter, so read on.但是您的方法似乎不合时宜,所以请继续阅读。

ScheduledExecutorService

You should not be trying to manage the timing of your executor service.您不应该试图管理执行程序服务的时间安排。 If you are calling Thread.sleep in conjunction with an executor service, you are likely doing something wrong.如果您将Thread.sleep与执行程序服务一起调用,则您可能做错了什么。

Nor should you be invoking new Thread .您也不应该调用new Thread The whole point of executor service is to let the framework manage the details of threading.执行器服务的重点是让框架管理线程的细节。 You are working too hard.你工作太辛苦了。

For repeated invocations of a task, use a ScheduledExecutorService .对于任务的重复调用,请使用ScheduledExecutorService

For more info, see the Oracle Tutorial , the class JavaDoc , and search Stack Overflow.有关详细信息,请参阅Oracle 教程JavaDoc 类并搜索 Stack Overflow。 This topic has been addressed many times already.这个话题已经讨论过很多次了。

Example app示例应用

Here is a brief example.这是一个简短的例子。

Use the Executors utility class to create your thread pool.使用Executors实用程序类来创建您的线程池。 We need only a single thread for you to make repeated calls to the database.我们只需要一个线程就可以让您重复调用数据库。 Looking at your partial code example, I cannot see why you were trying to run 5 threads.查看您的部分代码示例,我不明白您为什么要尝试运行 5 个线程。 If you are making a series of sequential calls to the database, you need only a single thread.如果您对数据库进行一系列顺序调用,则只需要一个线程。

Make your Runnable to call the database.让您的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() );
    }
}

CAUTION: Always wrap your run method's code with a try catch to catch any unexpected Exception or Error (a Throwable ).注意:始终使用try catch包装您的run方法的代码,以捕获任何意外的ExceptionError (一个Throwable )。 Any uncaught throwable reaching the executor will cause it to cease work.任何未捕获的投掷物到达执行者都会导致它停止工作。 The task will no longer be scheduled for further runs.该任务将不再被安排为进一步运行。

Instantiate that Runnable , and schedule it to run repeatedly.实例化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() );
    }
}

Notice how simple this is.请注意这是多么简单。

  • We defined a task, in an instantiated Runnable object.我们在实例化的Runnable对象中定义了一个任务。
  • We established an executor service backed by a single thread.我们建立了一个由单线程支持的执行程序服务。
  • We told that service to run our task repeatedly on our behalf, and we specified how often to run that task.我们告诉该服务代表我们重复运行我们的任务,并指定运行该任务的频率。
  • Eventually, we told the service to stop scheduling further executions of that task, and to close its thread pool.最终,我们告诉服务停止调度该任务的进一步执行,并关闭其线程池。

At no point did we deal directly with threads.我们从未直接处理线程。 We let the executors framework handle all the gnarly details of threading.我们让 executors 框架处理线程的所有细节。

Caution: You still need to make your Runnable code thread-safe if using multiple threads.注意:如果使用多线程,您仍然需要使Runnable代码线程安全 The executor framework is extremely slick and helpful, but it is not magic.执行器框架非常灵活和有用,但它并不神奇。 To learn about thread-safety and concurrency in Java, read this excellent book annually: Java Concurrency in Practice by Brian Goetz, et al.要了解 Java 中的线程安全性和并发性,请每年阅读这本优秀的书:Brian Goetz 等人的Java Concurrency in Practice

When run.跑的时候。

Starting app.启动应用程序。 2019-03-21T19:46:09.531740Z 2019-03-21T19:46:09.531740Z

Querying the database now.现在查询数据库。 2019-03-21T19:46:09.579573Z 2019-03-21T19:46:09.579573Z

Querying the database now.现在查询数据库。 2019-03-21T19:46:14.585629Z 2019-03-21T19:46:14.585629Z

Querying the database now.现在查询数据库。 2019-03-21T19:47:59.647485Z 2019-03-21T19:47:59.647485Z

Querying the database now.现在查询数据库。 2019-03-21T19:48:04.650555Z 2019-03-21T19:48:04.650555Z

Ending app.结束应用程序。 2019-03-21T19:48:09.579407Z 2019-03-21T19:48:09.579407Z

Skip the connection pool跳过连接池

In my experience, the need for a database connection pool is exaggerated by many folks.根据我的经验,许多人夸大了对数据库连接池的需求。 There are several pitfalls to using a connection pool.使用连接池有几个陷阱。 And I find establishing a database connection is not nearly as expensive as many claim, especially if local on the same machine.而且我发现建立数据库连接并不像许多人声称的那样昂贵,尤其是在同一台机器上本地时。

So I suggest you skip the connection pool for now.所以我建议你暂时跳过连接池。 Get your code working reliably while using fresh connections.让您的代码在使用新连接的同时可靠地工作。

If you can later prove a bottleneck in performance because of database connections, then consider a pool.如果您稍后可以证明由于数据库连接而导致性能瓶颈,那么请考虑使用池。 And verify it actually helps.并验证它确实有帮助。 Otherwise you would be committing the sin of premature optimization.否则,您将犯下过早优化的罪过。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM