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

Java 防止多个异步调用同时进行而不阻塞,java,design-patterns,concurrency,Java,Design Patterns,Concurrency,我的主要问题是: while (true) { if (previous 'doWorkAsync' method is not still in flight) { doWorkAsync() // this returns immediately } wait set amount of time } 我想到了几个解决方案: 直到doWorkAsync()完成。这对我来说是不可取的,有几个原因。 它(可能)会导致在“等待某个设置的时间量”行中等待的时间比我实际需要的时

我的主要问题是:

while (true) {
  if (previous 'doWorkAsync' method is not still in flight) {
    doWorkAsync()  // this returns immediately
  }
  wait set amount of time
}
我想到了几个解决方案:

  • 直到doWorkAsync()完成。这对我来说是不可取的,有几个原因。 它(可能)会导致在“等待某个设置的时间量”行中等待的时间比我实际需要的时间长(例如,如果doWorkAsync需要5秒,而设置的等待时间是10秒,这将导致调用之间等待15秒,这不是我想要的)。当然,我可以通过等待更少的时间来解释这一点,但不知何故,它只是感觉笨重。 它也不必要地束缚了这条线。该线程不必等待此任务返回,而可以处理其他工作,例如进行配置更新,以便下次调用doWorkAsync()时有新数据

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

  • 2是正确的方法,还是有更好的方法来解决这个问题

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


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

    想想你想要的是什么


    你不想让这些东西同时运行有什么原因吗?如果你想做的是连锁,你可以使用期货。具有可组合的未来和可映射的未来。

    最简单的方法是使用Java中已有的
    wait
    notifyAll
    方法。您所需要做的就是使用一个
    AtomicBoolean
    作为标志并在其上阻塞,直到另一个
    线程告诉您发生了变化

    这与您的方法的区别在于,阻塞线程不做任何事情,而轮询线程使用CPU时间

    下面是一个使用两个
    线程
    的简单示例-提交
    可运行的
    第一个
    ”,并等待
    完成
    ,直到
    可运行的
    第二个
    ”通知它已更改标志

    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
    每次进入循环时都会打印“waiting”,如果运行代码,则只打印一次。第二点需要注意的是,
    First
    会立即对标志的更改做出反应,因为当标志更改时,它会被告知唤醒并重新检查

    我在
    InterruptedException
    块中使用了
    return
    ,您可能希望使用
    Thread.currentThread().interrupt()
    ,这样,如果进程被错误中断,它就不会死掉

    更高级的方法是使用
    锁定
    条件

    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();
    
        }
    }
    
    在这种情况下,
    首先
    锁定
    对象上获取一个锁,然后立即调用
    等待
    条件
    。在
    条件下释放锁并阻塞

    Second
    然后获取
    lock
    上的锁,并调用
    条件下的
    signalAll
    ,该条件首先唤醒

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

    编辑

    OP希望使用指定的周期调用方法
    doworksync
    ,如果该方法花费的时间少于该周期,则进程必须等待。如果该方法需要更长的时间,则应在之后立即再次调用该方法

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

    在任何情况下,该方法都不应同时运行多次

    最简单的方法是从
    ScheduledExecutorService
    调用该方法,
    Runnable
    将包装该方法,并在
    未来的
    上调用
    get
    ——阻止计划执行器,直到完成

    这保证了调用该方法时,调用间隔至少为
    WAIT\u TIME\u调用间隔\u SECS
    delay

    然后安排另一个任务,在设定的时间后杀死第一个任务

    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);
    
    final ScheduledExecutorService ScheduledExecutorService=Executors.newSingleThreadScheduledExecutor();
    final Future taskHandle=scheduledExecutorService.scheduleAtFixedRate(new Runnable()){
    @凌驾
    公开募捐{
    final ListenableFuture lf=doworksync();
    试一试{
    doWorkAsync().get();
    }捕获(中断异常例外){
    Thread.currentThread().interrupt();
    }捕获(ExecutionException ex){
    抛出新的运行时异常(ex);
    }
    }
    },0,呼叫之间的等待时间(秒,时间单位为秒);
    scheduledExecutorService.schedule(新的Runnable(){
    @凌驾
    公开募捐{
    taskHandle.cancel(假);
    }
    },总时间秒,时间单位秒);
    

    最好的解决方案是在
    ScheduledExecutorService
    上调用原始
    Runnable
    ,而不是在另一个执行器上调用它并阻塞
    ListenableFuture

    以零时间连接异步工作线程有什么不对out@EliAlgranti我不想阻止。Thread.join()将阻塞,直到异步任务完成。您处于无限循环中,正在休眠。阻塞怎么会是个问题?@user949300,是的,我想我是在多想了一点之后才意识到这一点的。我认为我下面的解决方案解决了这个问题。让我知道你的想法。@knighty Thread.join(0)不会阻止我,我不会