如何使用 ScheduledExecutorService 实现固定速率轮询器?

Posted

技术标签:

【中文标题】如何使用 ScheduledExecutorService 实现固定速率轮询器?【英文标题】:How to implement a fixed rate poller with ScheduledExecutorService? 【发布时间】:2013-03-03 23:52:05 【问题描述】:

给定以下类:

public class Poller implements Runnable 
    public static final int CORE_POOL_SIZE = 4;

    public boolean running;
    public ScheduledExecutorService ses;

    public void startPolling() 
        this.ses = Executors.newScheduledThreadPool(CORE_POOL_SIZE);
        this.ses.scheduleAtFixedRate(this, 0, 1, TimeUnit.SECONDS);
    

    public void run() 
        running = true;
        // ... Do something ...
        running = false;
    

ScheduledExecutorService 的核心线程池大小为 4,但会创建多个轮询线程吗?既然 this 被传递到 scheduleAtFixedRate 中,这是否意味着永远只有一个线程 - 还是会在幕后发生更复杂的事情?

还有 2 个额外问题:-

    running 应该是static吗? CORE_POOL_SIZE 是多余的吗?

【问题讨论】:

运行不能是静态的 running 指示该特定线程是否正在运行,因此它必须是实例变量 - 不能是静态的。 谢谢。但我的困惑是我只会有一个 poller 实例 - 这是否意味着调度程序只会使用一个线程? 不会有多个线程。事实上,SES 内部的工作线程管理几乎是无用的。在您的情况下,只有一个 Runnable,只有当它们运行的​​时间超过其固定速率间隔时,拥有更多线程才有意义。但是,runnable 的下一次调用只会在它运行后安排,因此您不能同时运行同一个 runnable 的多次调用。 @RalfH 情况并非如此——初始延迟为 0,我相信至少会立即创建两个线程(一个现在运行,一个稍后运行)。并且在执行新的运行时,将使用整个核心池大小。 【参考方案1】:

ScheduledExecutorService 的核心线程池大小为 4,但是否会创建多个轮询线程?

这取决于 - 如果您运行程序足够长的时间,它可能会创建 4 个线程。如果您在只运行一次或两次计划任务后退出,您可能只会看到 2 或 3 个线程。

为什么重要?

监控线程创建的一种方法是提供您自己的ThreadFactory

this.ses = Executors.newScheduledThreadPool(CORE_POOL_SIZE, new ThreadFactory() 

            @Override
            public Thread newThread(Runnable r) 
                System.out.println("Creating thread");
                return new Thread(r);
            
        ); 

跑步应该是静态的吗?

这取决于您想要实现的目标...由于您在示例中并没有真正使用它,因此很难说。例如,如果您有多个 Poller 实例并且您不希望它们同时运行,则可能需要将其设为静态。

不管是静态的还是非静态的,如果你把它作为一个标志,你应该让它变得易变以确保可见性。

CORE_POOL_SIZE 是多余的吗?

不确定你的意思。它是一个强制参数,因此您需要提供一个值。如果您确定不会同时运行两个执行,那么您只能有一个线程。这也将阻止并发执行(因此,如果一个计划任务需要启动但另一个已在运行,则新任务将被延迟)。

【讨论】:

感谢您的帮助。不过还是有点迷茫。我只会实例化一个轮询器。那么如何创建额外的线程呢?关于冗余 q,我从回复中推测一次只能运行一个线程 - 那么在池中拥有多个线程有什么意义呢? @SteveChambers 它们之所以被创建,是因为 ScheduledThreadPoolExecutor 会创建新线程,直到达到核心池大小。您只需要一个线程的唯一原因是如果您想阻止并发执行任务(线程创建非常便宜,从性能/内存的角度来看,拥有 1 或 4 个线程不会产生太大影响)。如果没有,允许多个线程将有助于您的任务“按时”运行,即使之前计划的任务仍在运行。 我想我可能在这里遗漏了一些东西!假设我实例化poller,然后调用poller.startPolling()。它将自己 (this) 传递给 scheduleAtFixedRate()running 不是静态的,而是指单个 poller 实例,所以如果有多个线程,poller 代表哪一个?我如何知道其他人是否正在运行? @SteveChambers 您的问题有点令人困惑。您的 Runnable 只有一个实例,但多个线程可以同时运行它。 哦,我明白了!认为您可能刚刚指出了我所有困惑的根源。我现在认为running 标志可能没用,因为它可以由线程#1 启动打开,然后在线程#2 完成时再次直接关闭,所以即使线程#1 尚未完成,false 也是如此。 【参考方案2】:

你为什么把你的执行器服务放在Runnable 类中? 您应该将 ScheduledExecutorService 分隔为 Singleton,而不是作为可运行类的变量。

提醒这个ScheduledExecutorService是一个线程容器,所以当你编码这个

this.ses = Executors.newScheduledThreadPool(CORE_POOL_SIZE);

当你输入这段代码时,它会同时根据大小的值创建很多线程

this.ses.scheduleAtFixedRate(this, 0, 1, TimeUnit.SECONDS);

ScheduledExecutorService 将随机选择一个空闲的线程每 1 秒运行一次此类,直到完成。如果您将sleep 放入运行方法中,该值长于传递给预定线程的周期时间值,则在第一个线程完成之前它不会创建另一个线程。因此,如果您希望多个线程同时运行此Poller,则创建多个Poller 实例并将其传递给ScheduledExecutorService

CORE_POOL_SIZE 对我来说不是多余的,最好是从配置文件中获取的常量。

跑步应该是静态的吗? 这取决于你需要什么。如果您打算创建Poller 的多个实例,那么您不应该

【讨论】:

【参考方案3】:

scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)

此方法安排任务定期执行。任务在initialDelay后第一次执行,然后每次周期到期时循环执行。

如果给定任务的任何执行引发异常,则不再执行该任务。如果没有抛出异常,任务将继续执行,直到 ScheduledExecutorService 关闭。

如果一个任务的执行时间比其计划执行之间的时间长,则下一次执行将在当前执行完成后开始。计划任务一次不会被多个线程执行。

【讨论】:

以上是关于如何使用 ScheduledExecutorService 实现固定速率轮询器?的主要内容,如果未能解决你的问题,请参考以下文章

[精选] Mysql分表与分库如何拆分,如何设计,如何使用

如果加入条件,我该如何解决。如果使用字符串连接,我如何使用

如何使用本机反应创建登录以及如何验证会话

如何在自动布局中使用约束标识符以及如何使用标识符更改约束? [迅速]

如何使用 AngularJS 的 ng-model 创建一个数组以及如何使用 jquery 提交?

如何使用laravel保存所有行数据每个行名或相等