防止多个异步调用同时进行而不会阻塞

时间:2013-04-07 01:36:27

标签: java design-patterns concurrency

这基本上是我的问题:

while (true) {
  if (previous 'doWorkAsync' method is not still in flight) {
    doWorkAsync()  // this returns immediately
  }
  wait set amount of time
}

我想到了几种解决方案:

  1. 阻止,直到doWorkAsync()完成。出于某些原因,这对我来说是不可取的。 它(可能)导致在“等待一定时间”行中等待的时间超过我真正需要的时间(例如,如果doWorkAsync需要5秒,并且设定的等待时间为10秒,这将导致15秒在电话之间等待,这不是我想要的)。当然,我可以通过等待更少的时间来解释这一点,但不知何故它只是感觉笨重。 它还不必要地绑定了这个线程。该线程可以处理其他工作,而不是等待此任务返回,例如进行配置更新,以便下一次调用doWorkAsync()具有新数据。

  2. 使用门控机制。想到的最简单的实现是布尔值,在调用doWorkAsync()之前设置,在doWorkAsync()完成时取消设置。这基本上就是我现在正在做的事情,但我不确定它是否是一种反模式?

  3. #2是正确的方法,还是有更好的方法来解决这个问题?

    编辑:如果有帮助,doWorkAsync()会返回一个ListenableFuture(番石榴)。

    最初的问题可能不是100%清楚。这是关键。如果异步请求在给定的超时之前完成,则此代码将始终有效。但是,如果异步任务需要SET_AMOUNT_OF_TIME + epsilon完成,那么此代码将根据需要休眠两倍,这正是我要避免的。

2 个答案:

答案 0 :(得分:1)

最简单的方法是使用Java中已有的waitnotifyAll方法。您需要做的就是使用AtomicBoolean作为标记并阻止它,直到另一个Thread告诉您某些内容已发生变化。

它和你的方法之间的区别在于被阻塞的线程不做任何事情,而轮询线程使用CPU时间。

以下是使用两个Thread的简单示例 - RunnableFirst”已提交,等待done直到Runnable“{ {1}}“通知它已更改标记。

Second

public class App { private static final AtomicBoolean done = new AtomicBoolean(false); private static final class First implements Runnable { @Override public void run() { while (!done.get()) { System.out.println("Waiting."); synchronized (done) { try { done.wait(); } catch (InterruptedException ex) { return; } } } System.out.println("Done!"); } } private static final class Second implements Runnable { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException ex) { return; } done.set(true); synchronized (done) { done.notifyAll(); } } } public static void main(String[] args) throws InterruptedException { final ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.submit(new First()); Thread.sleep(1000); executorService.submit(new Second()); executorService.shutdown(); } } 来电只是为了表明可以进行任意长度的任务,显然不是必需的。

需要注意的是sleep每次进入循环时打印“等待”,如果运行代码,它只打印一次。需要注意的第二件事是First会立即响应标志的更改,因为它被告知唤醒并在标志更改时重新检查。

我在First块中使用了return,您可能希望使用InterruptedException代替,以便在虚假中断过程中不会死亡。

更高级的方法是使用Thread.currentThread().interrupt()Lock

Condition

在这种情况下public class App { private static final Lock lock = new ReentrantLock(); private static final Condition condition = lock.newCondition(); private static final class First implements Runnable { @Override public void run() { lock.lock(); System.out.println("Waiting"); try { condition.await(); } catch (InterruptedException ex) { return; } finally { lock.unlock(); } System.out.println("Done!"); } } private static final class Second implements Runnable { @Override public void run() { lock.lock(); try { Thread.sleep(1000); condition.signalAll(); } catch (InterruptedException ex) { return; } finally { lock.unlock(); } } } public static void main(String[] args) throws InterruptedException { final ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.submit(new First()); Thread.sleep(1000); executorService.submit(new Second()); executorService.shutdown(); } } 获取对First对象的锁定,立即调用Lock上的await。释放Condition上的锁和块。

Condition然后取消对Second的锁定,并在Lock上调用signalAll,唤醒Condition

First然后重新获取锁定并继续执行,打印“完成!”。

修改

OP希望在指定的时间段内调用方法First,如果方法花费的时间少于句点,那么进程必须等待。如果方法花费的时间更长,那么应该在之后立即再次调用该方法。

任务需要在一段时间后停止。

该方法不应同时运行多次。

最简单的方法是从doWorkAsync调用方法,ScheduledExecutorService将包装方法并在Runnable上调用get - 阻止预定的执行程序,直到它完成了。

这可以保证在至少 Future延迟的情况下调用该方法。

然后安排另一个任务,在一段时间后杀死第一个任务。

WAIT_TIME_BETWEEN_CALLS_SECS

最好的解决方案是在final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); final Future<?> taskHandle = scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { final ListenableFuture<Void> lf = doWorkAsync(); try { doWorkAsync().get(); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } catch (ExecutionException ex) { throw new RuntimeException(ex); } } }, 0, WAIT_TIME_BETWEEN_CALLS_SECS, TimeUnit.SECONDS); scheduledExecutorService.schedule(new Runnable() { @Override public void run() { taskHandle.cancel(false); } }, TOTAL_TIME_SECS, TimeUnit.SECONDS); 上调用原始Runnable,而不是在另一个执行程序上调用它并阻止ScheduledExecutorService

答案 1 :(得分:0)

认为你在寻找的是The Reactor Pattern

您是否有理由不希望这些东西同时运行?如果您想要做的是链接它们,您可以使用期货。 Akka具有可组合期货和可映射的期货。