4

We have a service implementation in our Spring-based web application that increments some statistics counters in the db. Since we don't want to mess up response time for the user we defined them asynchronous using Spring's @Async:

public interface ReportingService {

    @Async
    Future<Void> incrementLoginCounter(Long userid);

    @Async
    Future<Void> incrementReadCounter(Long userid, Long productId);
}

And the spring task configuration like this:

<task:annotation-driven executor="taskExecutor" />
<task:executor id="taskExecutor" pool-size="10" />

Now, having the pool-size="10", we have concurrency issues when two threads try two create the same initial record that will contain the counter.

Is it a good idea here to set the pool-size="1" to avoid those conflicts? Does this have any side affects? We have quite a few places that fire async operations to update statistics.

2
  • Ideally, any increment kind of operation, supposed to use thread safe, i.e. synchronize, just only for increment part. Rest of statement can be executed in Async. Commented Jun 24, 2013 at 15:12
  • 2
    If you do so, never ever use @Async for another kind of operation since it will use the same ThreadPoolExecutor, and thus be slown down by the ReportingService operations. Commented Jun 24, 2013 at 15:19

2 Answers 2

9

The side-effects would depend on the speed at which tasks are added to the executor in comparison to how quickly a single thread can process them. If the number of tasks being added per second is greater than the number that a single thread can process in a second you run the risk of the queue increasing in size over time until you finally get an out of memory error.

Check out the executor section at this page Task Execution. They state that having an unbounded queue is not a good idea.

If you know that you can process tasks faster than they will be added then you are probably safe. If not, you should add a queue capacity and handle the input thread blocking if the queue reaches this size.

Sign up to request clarification or add additional context in comments.

Comments

4

Looking at the two examples you posted, instead of a constant stream of @Async calls, consider updating a JVM local variable upon client requests, and then have a background thread write that to the database every now and then. Along the lines of (mind the semi-pseudo-code):

class DefaultReportingService implements ReportingService {

    ConcurrentMap<Long, AtomicLong> numLogins;

    public void incrementLoginCounterForUser(Long userId) {
        numLogins.get(userId).incrementAndGet();
    }

    @Scheduled(..)
    void saveLoginCountersToDb() {
        for (Map.Entry<Long, AtomicLong> entry : numLogins.entrySet()) {
            AtomicLong counter = entry.getValue();
            Long toBeSummedWithTheValueInDb = counter.getAndSet(0L);
            // ...
        }
    }   
}

2 Comments

this is a very good idea jukka. i think next time i will implement it this way. i accepted johns answer, just because the solution is based on my questions approach.
@fischermatte Did you try this approach? What happens if app crashes before all numLogins are written? Maybe this issue exists with the other solution(accepted answer)?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.