Java中的事件循环

10
我正在尝试为Nashorn (Java 8)编写事件循环,以便来自异步操作 (我启动的线程,例如连接到远程服务或执行长时间运算的线程) 的回调将被放入队列中并按顺序 (而不是并行) 执行。我通过将回调函数放在ConcurrentLinkedQueue上,并使用ScheduledExecutorService作为检查队列以执行回调的循环来实现它。
这个方法很好用,但我的问题是:
1)我可以使用多短的间隔而不会拖慢CPU?我将运行多个此类线程,并且它们必须彼此独立。因此,可能有50个线程都在运行它们自己的“事件循环”。例如,此执行程序会尝试每10毫秒运行一次可运行资源...
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(<cbRunnable>, 0, 10, TimeUnit.MILLISECONDS);

2) 这种方法是否比以下方式更具优势:

while (true) {
   // check queue and execute any callback...
}

3) 有更好的方法吗?


"不占用我的CPU"。这句话的意思是指在进行某项任务时,尽量避免让计算机的中央处理器(CPU)过度负荷运转,以免影响计算机的性能和稳定性。 - ControlAltDel
1
使用阻塞队列而不是轮询怎么样? - RealSkeptic
@ControlAltDel:我的意思是不要过度占用CPU。不要使用它的所有周期等。 - mister blinky
你的第二种方法没有固定的重试速率。所以你无法像第一种方法那样真正控制在调用之间等待10毫秒。 - Christian
@Christian - 但是假设我在第一种方法中将重试速率降低到1毫秒 - 这就是我的意思。我也可以在while循环中使用Thread.sleep(retryRate)...希望这样能澄清问题... - mister blinky
1个回答

11
答案完全取决于您在此块内所做的操作:
while (true) {
    // check queue and execute any callback...
}

如果队列检查会阻塞,直到有元素可用,则这是轮询的“最有效”方式,就 CPU 使用而言。 如果检查不会阻塞,则调用线程将自旋,并且在循环期间将运行一个硬件线程的全部容量 - 但是,这消除了同步成本,并将为您提供绝对最佳的响应时间。以下是一些示例: 阻塞队列(对 CPU 的负担最小,但代价是同步)
Queue<T> q = new LinkedBlockingQueue<>();
...
while(true){
    T t = q.take();
    //t will never be null, do something with it here.
}

非阻塞队列(对CPU最具负担,但没有同步成本)

Queue<T> q = new LinkedList<>();
...
while(true){
    T t;
    while((t = q.poll()) == null); //busy polling!
    //t will never be null, do something with it here
}

ScheduledExecutorService(定时执行器服务)

最后...如果您最终使用了定时执行器服务,那么您将在轮询之间强制等待一些非零等待时间。与完全的 BlockingQueue 实现相比,从 CPU 方面来说几乎同样有效率,但您也被迫承受响应时间上的损失,最多达到调度间隔时间。
对于您的应用程序来说,“最佳”选择将取决于您是否能够承担等待轮询之间的最小睡眠时间(我觉得您可以安排到微秒...?),或者您是否需要像繁忙轮询方案这样更快的东西。


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - mister blinky
1
有最佳的FIFO阻塞队列实现,它使用聪明的旋转等待和操作系统等待句柄的组合(无轮询),几乎没有开销。我乐观地认为SingleThreadExecutor实际上正在使用类似的方法。一个最优的SingleThreadScheduledExecutor将使用类似的方法,从旋转等待开始,等待接近上下文切换持续时间的间隔,但是然后,它将等待来自高性能定时器API的操作系统中断事件,而不是等待项目进入队列。 - Tamir Daniely

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接