首页 文章

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

提问于
浏览
10

我有一个应用程序,其中需要计算一定数量的东西 . 此计算函数具有注释@Async(来自Spring Framework),这使得可以在4个线程上运行这些计算 . 问题是我需要大约40000个这些计算,我想知道所有计算的开始和结束之间的时间,所以我看到调用计算函数的for循环之前和之后的时间 . 但是现在所有的计算都被放入队列中,因此for循环立即结束,时间就像1秒钟,而计算完成需要几个小时 . 我已经尝试将最大队列大小设置为大约100(也很好地减少了内存使用量),但这也没有解决方案,因为我将错过它花费的总时间中的最后100次计算 . 有没有办法在for循环之后暂停执行代码,直到所有线程完成其工作,但仍然能够使用@Async注释?

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

执行课程:

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.");
    }
}

以及执行计算的类:

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

这将导致以下输出(假设Foo中的代码执行速度非常快):

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

2 回答

  • 31

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

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

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

    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.");
    }
    

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

  • 1

    另一种方法是返回 ListenableFuture 并使用 CountDownLatch .

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

    此方案允许您避免为每个未来显式调用 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.");
        }
    

    }

相关问题