Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

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

Time it took for a lot of bla to execute: 0.0 seconds.
Bla!
Bla!
Bla!
Bla!
.
.
.
etc
like image 901
Erik Stens Avatar asked Dec 01 '10 12:12

Erik Stens


2 Answers

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

@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.

like image 118
skaffman Avatar answered Sep 20 '22 17:09

skaffman


An alternative is to return a ListenableFuture and to use 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.

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

}

like image 33
Brian Avatar answered Sep 20 '22 17:09

Brian