简体   繁体   English

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

[英]Prevent multiple asynchronous calls from being in flight simultaneously without blocking

Here's essentially my problem: 这基本上是我的问题:

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

A couple solutions come to mind for me: 我想到了几种解决方案:

  1. Block until doWorkAsync() completes. 阻止,直到doWorkAsync()完成。 This is not desirable to me for a few reasons. 出于某些原因,这对我来说是不可取的。 It (potentially) results in waiting longer than I really needed to in the 'wait some set amount of time' line (eg if doWorkAsync takes 5 seconds, and the set amount of waiting time is 10 seconds, this will result in 15 seconds of waiting between calls, which isn't what I wanted). 它(可能)导致在“等待一定时间”行中等待的时间超过我真正需要的时间(例如,如果doWorkAsync需要5秒,并且设定的等待时间为10秒,这将导致15秒在电话之间等待,这不是我想要的)。 Of course, I could account for this by waiting less time, but somehow it just feels clunky. 当然,我可以通过等待更少的时间来解释这一点,但不知何故它只是感觉笨重。 It also ties up this thread unnecessarily. 它还不必要地绑定了这个线程。 Instead of waiting for this task to come back, this thread could handle other work, like making config updates so the next call to doWorkAsync() has fresh data. 该线程可以处理其他工作,而不是等待此任务返回,例如进行配置更新,以便下一次调用doWorkAsync()具有新数据。

  2. Use a gating mechanism. 使用门控机制。 The easiest implementation that comes to mind is a boolean, set before calls to doWorkAsync(), and unset when doWorkAsync() completes. 想到的最简单的实现是布尔值,在调用doWorkAsync()之前设置,在doWorkAsync()完成时取消设置。 This is essentially what I'm doing now, but I'm not sure if it's an anti-pattern?? 这基本上就是我现在所做的,但我不确定它是否是反模式?

Is #2 the right way to go, or are there better ways to solve this problem? #2是正确的方法,还是有更好的方法来解决这个问题?

EDIT : If it helps, doWorkAsync() returns a ListenableFuture (of guava). 编辑 :如果有帮助,doWorkAsync()返回一个ListenableFuture(番石榴)。

The original question may not have been 100% clear. 最初的问题可能不是100%明确的。 Here's the crux. 这是关键。 If the async request finishes before the given timeout, this code will always work. 如果异步请求在给定的超时之前完成,则此代码将始终有效。 However, if the async task takes SET_AMOUNT_OF_TIME + epsilon to complete, then this code will sleep twice as long as necessary, which is what I'm trying to avoid. 但是,如果异步任务需要SET_AMOUNT_OF_TIME + epsilon完成,那么此代码将根据需要休眠两倍,这正是我要避免的。

The simplest way to do this is using the wait and notifyAll methods already in Java. 最简单的方法是使用Java中已有的waitnotifyAll方法。 All you need to do is use an AtomicBoolean as a flag and block on it until the another Thread tells you something has changed. 您需要做的就是使用AtomicBoolean作为标志并阻止它,直到另一个Thread告诉您某些内容已更改。

The difference between that and your approach is that a blocked thread doesn't do anything whereas a polling thread uses CPU time. 它与您的方法之间的区别在于,阻塞的线程不执行任何操作,而轮询线程使用CPU时间。

Here is a simple example using two Thread s - the Runnable " First " is submitted and it waits on done until the Runnable " Second " notifies that it has changed the flag. 下面是一个使用两个Thread的简单示例 - 提交RunnableFirst ”并等待done直到RunnableSecond ”通知它已经更改了标志。

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();

    }
}

The sleep calls are just to show that a task of arbitrary length can take place, obviously they are not required. sleep调用只是为了表明可以进行任意长度的任务,显然它们不是必需的。

The thing to note is that First prints "waiting" every time it enters the loop and, if you run the code, it only prints it once. 需要注意的是, First 每次进入循环时都会打印“等待”,如果运行代码,它只打印一次。 The second thing to note is that First reacts to the changing of the flag immediately as it is told to awake and recheck when the flag is changed. 需要注意的第二件事是,当标志被唤醒时, First会立即对标志的更改作出反应,并在标志更改时重新检查。

I have used return in the InterruptedException blocks, you may want to used Thread.currentThread().interrupt() instead so that the process doesn't die if it's spuriously interrupted. 我在InterruptedException块中使用了return ,你可能想要使用Thread.currentThread().interrupt()这样如果它被虚假中断,进程就不会死掉。

A more advanced approach is to use Lock and Condition 更高级的方法是使用LockCondition

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();

    }
}

In this situation First acquires a lock on the Lock object the immediately calls await on the Condition . 在这种情况下, First获取Lock对象的Lock ,立即调用await Condition The releases the lock and blocks on the Condition . 释放Condition上的锁和块。

Second then acquires a lock on the Lock and calls signalAll on the Condition which awakes First . 然后Second获取Lock上的Lock并在唤醒FirstCondition上调用signalAll

First then reacquires the lock and continues execution, printing "Done!". First重新获取锁定并继续执行,打印“完成!”。

EDIT 编辑

The OP would like to call the method doWorkAsync with a specified period, if the method takes less time than the period then the process has to wait. OP希望在指定的时间段内调用doWorkAsync方法,如果该方法花费的时间少于周期,则进程必须等待。 If the method takes longer then the method should be called again immediately after. 如果方法花费的时间更长,那么应该在之后立即再次调用该方法。

The task needs to be stopped after a certain time. 任务需要在一段时间后停止。

At no point should the method be running more than once simultaneously. 在任何时候,该方法都不应同时运行多次。

The easiest approach would be to call the method from a ScheduledExecutorService , the Runnable would wrap the method and call get on the Future - blocking the scheduled executor until it is done. 最简单的方法是从ScheduledExecutorService调用该方法, Runnable将包装该方法并调用Future上的get - 阻塞调度的执行程序直到完成。

This guarantees that the method is called with at least WAIT_TIME_BETWEEN_CALLS_SECS delay. 这保证了至少以 WAIT_TIME_BETWEEN_CALLS_SECS延迟调用该方法。

Then schedule another task that kills the first one after a set time. 然后安排另一个任务,在一段时间后杀死第一个任务。

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);

The best solution would be call the raw Runnable on a ScheduledExecutorService rather than calling it on another executor and blocking on the ListenableFuture . 最好的解决方案是在ScheduledExecutorService上调用原始Runnable ,而不是在另一个执行器上调用它,并在ListenableFutureListenableFuture

Think what you are looking for is The Reactor Pattern . 想想你正在寻找的是反应堆模式

Is there a reason you don't want these things running at the same time? 您是否有理由不希望这些东西同时运行? If what you want to do is chain them, you could use Futures. 如果您想要做的是链接它们,您可以使用期货。 Akka has Composable Futures and mappable ones. Akka拥有可组合期货和可映射期货。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 来自Web应用程序的异步调用 - Asynchronous calls from a webapp 异步Java:如何在不阻塞的情况下返回此值? - Asynchronous Java: How to return this without blocking? 如何使用AsyncRestTemplate同时进行多个调用? - How to use AsyncRestTemplate to make multiple calls simultaneously? WebFlux 中的 fromCallable 包装的多个阻塞调用 - Multiple blocking calls wrapped in fromCallable in WebFlux 异步JAX-WS多个调用 - Asynchronous JAX-WS multiple calls 使用什么异常来防止方法被多次调用? - What exception to use to prevent a method from being called multiple times? 防止托管Bean多次实例化 - Prevent Managed Bean From Being Instantiated Multiple Times 如何防止在重新打开活动时创建多个ScheduledExecutorServices - How to prevent multiple ScheduledExecutorServices from being created upon reopening activity 如何防止在Android应用程序中创建多个数据库? - How to prevent multiple databases from being created within Android app? Springboot:如何使用 WebClient 而不是 RestTemplate 来执行非阻塞和异步调用 - Springboot : How to use WebClient instead of RestTemplate for Performing Non blocking and Asynchronous calls
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM