2

I'm trying to implement concurrent method invocation in my Service class.

I've some methods annotated as @Async in my service class and I'm trying to call all these methods concurrently. But the methods are executing sequentially.

This is my service class(dummy):

@Service public class TestService {

public SomeDataType getSOmeDataType() {
        try {
            List<DataType> a = retrieveDataA().get();
            List<DataType> b = retrieveDataB().get();
            List<DataType> c = retrieveDataC().get();
            List<DataType> d = retrieveDataD().get();
            List<DataType> e = retrieveDataE().get();           
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        catch (ExecutionException e) {
            e.printStackTrace();
        }
        return referralDetailsReferenceData;
    }

@Async 
    private Future<List<DataType>> retrieveDataA() {
        //method logic
    }

@Async 
    private Future<List<DataType>> retrieveDataB() {
        //method logic
    }

@Async 
    private Future<List<DataType>> retrieveDataC() {
        //method logic
    }

@Async 
    private Future<List<DataType>> retrieveDataD() {
        //method logic
    }

@Async 
    private Future<List<DataType>> retrieveDataE() {
        //method logic
    }

This is my spring config:

<bean id="executorService" class="java.util.concurrent.Executors" factory-method="newFixedThreadPool">
    <constructor-arg value="10" />
</bean>

<task:executor id="threadPoolTaskExecutor" pool-size="10" />

<task:annotation-driven executor="executorService" />

When "getSomeDataType" is executed the methods are invoked sequentially.

I'm new to @Async and concurrent execution in Spring, so I'm sure I'm doing something silly. But I'm not able to figure it out.

Any suggestions is highly appreciated.

tarares
  • 392
  • 4
  • 10
  • 27

1 Answers1

23

The issue is that you're calling the methods internally so they're not proxied. For the @Async to work you need to retrieve the object from your application context and invoke the methods on the retrieved copy. Invoking it internally will not work.

The same thing happens if you try to invoke internal @Transactional methods. See the Note: section at the end of the Spring docs explaining @Transactional for details.

Additionally, the way you're immediately calling .get() on the Future return values is incorrect. If you want them to happen in parallel you should submit all the tasks then retrieve each one via .get().

The usual approach is to handle the proxying issue is to create a separate service class that gets TestService injected into it and calls the @Async service methods directly:

@Service public class TestServiceHelper {
    @Autowired
    TestService testService;

    public SomeDataType getSOmeDataType() {
        try {
            // Invoke all of them async:
            Future<List<DataType>> a = testService.retrieveDataA();
            Future<List<DataType>> b = testService.retrieveDataA();
            Future<List<DataType>> c = testService.retrieveDataA();
            Future<List<DataType>> d = testService.retrieveDataA();
            Future<List<DataType>> e = testService.retrieveDataA();

            // Wait for each sequentially:
            List<DataType> aList = a.get();
            List<DataType> bList = b.get();
            List<DataType> cList = c.get();
            List<DataType> dList = d.get();
            List<DataType> eList = e.get();

            // do work with lists here ...
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        catch (ExecutionException e) {
            e.printStackTrace();
        }
        return referralDetailsReferenceData;
    }
}

An alternative to the above separate service bean (quite hackish/not recommended!) is to inject the object into itself and use the injected copy to invoke the @Async methods.

sehrope
  • 1,777
  • 13
  • 16
  • Hey one question..I should call the getSOmeDataType() from my controller right? – tarares Jul 18 '13 at 23:44
  • 1
    Yes. More generally you can call it from anywhere that the bean `TestServiceHelper` has been injected (so controllers or other service beans as well). – sehrope Jul 18 '13 at 23:52
  • I tried executing the methods the way you described in your answer. Also I added System.out.println before and after each method's logic inside a method. And I'm getting the following output: 1 Start **************** 1 End **************** 2 Start **************** 2 End **************** 3 Start **************** 3 End **************** 4 Start **************** 4 End **************** Is it not pointing to sequential execution of method. Please advise. (Sorry not able to show each output in next line..it sucks) – tarares Jul 19 '13 at 00:30
  • Testing async code is harder than regular sequential code. If each method itself is very quick then it's highly likely it'll appear sequential. To verify that each is running in its own thread add the current thread to your debug print statements (`Thread.currentThread().getName())` and add a `Thread.sleep(...)` to the individual `retrieveDataX()` methods so they don't finish immediately. That should show if it's actually running concurrently. – sehrope Jul 19 '13 at 01:01
  • @sephore I tried the way you suggested in your comment. Below is one of the methods: I added Thread.sleep(5000) and System.out.println(Thread.currentThread().getName() + ":: 1 Start ****************") (similarly in all sysouts). The output is: 2::1 Start **************** 2::1 End **************** 2::2 Start **************** 2::2 End **************** 2::3 Start **************** 2::3 End **************** 2::4 Start **************** 2::4 End **************** Seems like all the methods are running in same thread. Its killing me.. :( – tarares Jul 19 '13 at 01:30
  • The only other thing that comes to mind is depending on the proxy types Spring creates you may have to define a service interface. Otherwise it won't be able to create the proxy (if you're using the default of JDK proxies). If that doesn't work create an [SSCE](http://sscce.org/) of your code. Also check out the Spring [tutorial](http://blog.springsource.org/2010/01/05/task-scheduling-simplifications-in-spring-3-0/) and it's associated [sample code](https://src.springframework.org/svn/spring-samples/task-basic/trunk/). – sehrope Jul 19 '13 at 02:10
  • You just saved me hours of work! I was dealing with PipedInput/OutputStream and they get locked if called from the same thread, so you can imagine how desperate was I when I realised my @Async calls were made in the same thread. – Panthro Oct 26 '15 at 18:02