【问题标题】:ScheduledExecutorService running many threads when I created it using Executors.newSingleThreadScheduledExecutor当我使用 Executors.newSingleThreadScheduledExecutor 创建 ScheduledExecutorService 时,它​​运行了许多线程
【发布时间】:2016-06-18 14:34:48
【问题描述】:

上下文:

  • 这是一个Jersey 应用程序
  • Tomcat 服务器上运行
  • 使用Hibernate 连接到MySQL 数据库
  • 全部在Netbeans

我正在创建一个ScheduledExecutorService 来每天在特定时间运行。但是,当它运行时,它会创建许多与数据库的连接。我想这是因为当我重新运行应用程序时线程没有被破坏,所以很多ScheduledExecutorServices 在 JVM 中建立并同时执行。但是,我不确定是什么原因。

我使用仅创建守护线程的ThreadFactory 创建ScheduledExecutorService,因此当我重新运行应用程序时,我想所有守护线程都应该被销毁,以便只存在一个ScheduledExecutorService

这里是ScheduledExecutorService的创建:

ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = Executors.defaultThreadFactory().newThread(r);
            t.setDaemon(true);
            return t;
        }
    });

这是它每天在特定时间的运行方式:

public void startExecutionAt(int targetHour, int targetMin, int targetSec){
        long delay = computeNextDelay(targetHour, targetMin, targetSec);

        mExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                Thread t = new Thread(new CustomRunnable());
                t.setDaemon(false);
                t.start();
                startExecutionAt(targetHour, targetMin, targetSec);
            }
        }, delay, TimeUnit.SECONDS);
    }

我知道它正在创建许多与MySQL 的连接,因为在P6Spy 日志中,当CustomRunnable 只运行一个Hibernate 查询从其中选择5 条记录时,执行CustomRunnable 时会产生许多连接和查询一张桌子。

对可能发生的事情有任何想法吗?

【问题讨论】:

    标签: java multithreading hibernate


    【解决方案1】:

    你显然滥用了你的ScheduledExecutorService,这是你应该做的:

    public void startExecutionAt(int targetHour, int targetMin, int targetSec){
        long delay = computeNextDelay(targetHour, targetMin, targetSec);
    
        mExecutorService.scheduleAtFixedRate(
            new CustomRunnable(), 
            1000L * delay, 
            TimeUnit.DAYS.toMillis(1), 
            TimeUnit.MILLISECONDS
        );
    }
    

    你调用这个方法一次,它会以delay作为初始延迟启动你的任务,然后每天在同一时间。

    更多详情here.

    确实,您当前在任何时候执行任务时都会创建一个新线程,然后再次调用该方法来安排下一次执行,这不是您应该做的,该任务已经在池中执行,无需创建新线程Thread 此外,当您可以直接使用 ScheduledExecutorService 进行时,无需再次手动安排。

    响应更新:

    由于您的情况似乎更复杂,因此您应该这样做:

    public void startExecutionAt(int targetHour, int targetMin, int targetSec){
        long delay = computeNextDelay(targetHour, targetMin, targetSec);
    
        mExecutorService.schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    new CustomRunnable().run();
                } finally {
                    startExecutionAt(targetHour, targetMin, targetSec);
                }
            }
        }, delay, TimeUnit.SECONDS);
    }
    

    这样您就不会无缘无故地创建和启动新线程。

    您的问题的第二部分应该通过在初始化和销毁​​的ServletContext 上分别正确启动和关闭您的ScheduledExecutorService 来解决,换句话说,管理您的调度程序的类应该是ServletContextListener

    public class MyServletContextListener implements ServletContextListener {
        private ScheduledExecutorService executorService;
        public void contextInitialized(ServletContextEvent sce) {
            executorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = Executors.defaultThreadFactory().newThread(r);
                    t.setDaemon(true);
                    return t;
                }
            });
        }
    
        public void contextDestroyed(ServletContextEvent sce) {
            executorService.shutdownNow();
        }
    
        public void startExecutionAt(int targetHour, int targetMin, int targetSec){
            // code defined above here
        }
    }
    

    如果您需要服务使其工作,请使用 CDI 通过注入获取它们。

    【讨论】:

    • 这似乎没有考虑夏令时
    • 另外,当我重新运行我的应用程序(即重新部署)时,守护程序线程是否应该终止?现在我正在开发并不断地重新运行我的应用程序。我使用仅创建守护线程的ThreadFactory 创建ScheduledExecutorService 的原因是因为我认为重新部署程序时守护线程应该终止;但事实并非如此。你知道上面的代码是怎么阻止这种情况发生的吗?
    • 您还需要获取通过调用executorService.schedule() 创建的ScheduledFuture 的引用,并在contextDestroyed() 中取消它。 executorService.shutdown() 默认不取消非重复任务
    猜你喜欢
    • 1970-01-01
    • 2011-10-14
    • 1970-01-01
    • 2014-10-11
    • 1970-01-01
    • 2021-01-25
    • 2012-10-13
    • 2016-01-22
    • 1970-01-01
    相关资源
    最近更新 更多