java @Async 阻止一个线程继续,直到其他线程完成

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4324212/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 05:52:29  来源:igfitidea点击:

@Async prevent a thread to continue until other thread have finished

javamultithreadingspringasynchronousqueue

提问by FinalArt2005

I have an application where for a certain number of times something needs to be calculated. This calculation function has the annotation @Async (from the Spring Framework), that makes it possible to run these calculations on 4 threads. The problem is that I need about 40000 of these calculations and I want to know the time between the start and end of all the calculations, so I see what time it is before and after the for-loop that calls the calculation functions. But now all the calculations are put in a queue, so the for loop finishes immediately and the time is something like 1 second, while it takes a couple of hours for the calculations to complete. I've tried setting a max queue size to about 100 (also good to reduce memory usage) but this is also no solution since I'll miss the last 100 calculations in the total time it takes. Is there a way to pause the executing code just after the for loop until all threads have finished doing their work, but still being able to use the @Async annotation?

我有一个应用程序,需要计算某些次数。这个计算函数有 @Async 注释(来自 Spring 框架),这使得在 4 个线程上运行这些计算成为可能。问题是我需要大约 40000 次这些计算,我想知道所有计算的开始和结束之间的时间,所以我看看在调用计算函数的 for 循环之前和之后的时间。但是现在所有的计算都放在一个队列中,所以 for 循环立即完成,时间大约是 1 秒,而计算需要几个小时才能完成。我已经尝试将最大队列大小设置为大约 100(也可以减少内存使用量),但这也不是解决方案,因为我会错过总时间中的最后 100 次计算。

This is some code that illustrates the same problem:

这是一些说明相同问题的代码:

Executing class:

执行类:

public class Foo {
    public void executeBlaALotOfTimes() {
        long before = System.currentTimeMillis();

        for (int i = 0; i<40000; i++) {
            executeBla();
        }

        long after = System.currentTimeMillis(); 

        System.out.println("Time it took for a lot of bla to execute: " + (after - before) / 1000.0 + " seconds.");
    }
}

And the class that performs the calculations:

以及执行计算的类:

@Service
public class Bar {
    @Async
    public void executeBla() {
        System.out.println("Bla!");
    }
}

This would result in the following output (assuming the code in Foo executes infinitely fast):

这将导致以下输出(假设 Foo 中的代码无限快地执行):

Time it took for a lot of bla to execute: 0.0 seconds.
Bla!
Bla!
Bla!
Bla!
.
.
.
etc

回答by skaffman

If you need to wait for the executions to finish, then you can return a Futureas a return value, e.g.

如果您需要等待执行完成,那么您可以返回 aFuture作为返回值,例如

@Async
public Future<Void> executeBla() {
    System.out.println("Bla!");
    return new AsyncResult<Void>(null);
}

This is slightly artificial, since there's no actual value being returned, but it will still allow the calling code to wait for all executions to finish:

这有点人为,因为没有返回实际值,但它仍然允许调用代码等待所有执行完成:

public void executeBlaALotOfTimes() {
    long before = System.currentTimeMillis();

    Collection<Future<Void>> futures = new ArrayList<Future<Void>>();

    for (int i = 0; i<40000; i++) {
        futures.add(executeBla());
    }

    for (Future<Void> future : futures) {
        future.get();
    }

    long after = System.currentTimeMillis(); 

    System.out.println("Time it took for a lot of bla to execute: " + (after - before) / 1000.0 + " seconds.");
}

Here, the first loop fires off the async tasks and stores the futures in a list. The seconds loop then iterates over the futures, waiting for each one to finish.

在这里,第一个循环触发异步任务并将期货存储在列表中。然后秒循环遍历期货,等待每个期货完成。

回答by Brian

An alternative is to return a ListenableFutureand to use a CountDownLatch.

另一种方法是返回 aListenableFuture并使用 a CountDownLatch

@Async
public ListenableFuture<Void> executeBla() {
    try {
        System.out.println("Bla!");
        return AsyncResult.forValue(null);
    } catch (Throwable t) {
        return AsyncResult.forExecutionException(t);
    }
}

This scenario allows you to avoid explicitly calling future.get()for each future. You accomplish this by adding success and failure callbacks which in turn decrement the CountDownLatch, which was created exactly for this purpose.

这种情况允许您避免显式调用future.get()每个未来。您可以通过添加成功和失败回调来实现这一点,这反过来又会减少CountDownLatch,这正是为此目的而创建的。

public void executeBlaALotOfTimes() {
    long before = System.currentTimeMillis();

    int numExecutions = 40000;
    CountDownLatch countDownLatch = new CountDownLatch(numExecutions);

    for (int i = 0; i<numExecutions; i++) {
        ListenableFuture<Void> future = executeBla();
        future.addCallback(
            aVoid -> countDownLatch.countDown(), 
            throwable -> countDownLatch.countDown()
        );
    }

    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        // Handle exception
    } finally {
        long after = System.currentTimeMillis();
        System.out.println("Time it took for a lot of bla to execute: " + (after - before) / 1000.0 + " seconds.");
    }

}

}