@Async可以避免一个线程在另一个线程完成之前继续执行。

10
我有一个应用程序,在某些情况下需要进行一定次数的计算。这个计算函数有一个来自Spring Framework的@Async注释,可以在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代码执行非常快,那么将会得到以下输出:

执行大量bla所用的时间:0.0秒。
Bla!
Bla!
Bla!
Bla!
.
.
.
等等

这是Spring的@Async吗? - skaffman
抱歉,是的,这是使用Spring @Async。 - Erik Stens
没有必要展示整个函数,因为它变得有点庞大了(是的,我知道这是设计缺陷,我很快就会进行更正),所以现在我将提供一个相同设置的简化版本,它会产生相同的问题。 - Erik Stens
2个回答

35

如果需要等待执行完成,那么可以返回一个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.");
}

在这里,第一个循环触发异步任务并将其future存储在列表中。第二个循环遍历future,等待每个任务完成。


3

一种替代方案是返回一个 ListenableFuture 并使用 CountDownLatch

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

这种情况下,您可以避免为每个future显式调用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.");
    }

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接