如何使用 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 实现固定速率轮询器?的主要内容,如果未能解决你的问题,请参考以下文章
如何在自动布局中使用约束标识符以及如何使用标识符更改约束? [迅速]