9

I have a singleton class (@Service annotated). This class has a method which takes 200/300ms to execute.

This method is annotated with @Cacheable and synchronized.

@Cacheable(value="nextPlaying", key = "#startingFrom.getYear() + #startingFrom.getMonth() + #startingFrom.getDay() + #startingFrom.getHours() + #startingFrom.getMinutes() + #locale.getLanguage()")
public synchronized List<Match> getNextPlaying(Date startingFrom, Locale locale)

By launching multiple threads calling this method I see that for these 200/300ms until the result isn't cached, it executes again and again the method until is cached. Seems that @Cacheable annotation doesn't take synchronized into account... Is this a bug?

Andres
  • 10,561
  • 4
  • 45
  • 63
ianaz
  • 2,490
  • 3
  • 28
  • 37
  • 1
    Support for synchronized caches is added in Spring 4.3: https://spring.io/blog/2016/03/04/core-container-refinements-in-spring-framework-4-3 – Thomas Apr 07 '16 at 12:20

2 Answers2

34

Good news, spring framework 4.3 has provided a way to support your need, by adding sync=true in @Cacheable.

Clement.Xu
  • 1,228
  • 2
  • 15
  • 24
  • 2
    here > https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/integration.html#cache-annotations-cacheable-synchronized also says your cache provider must support synchronized cache implementation... – Dzmitry Prakapenka Jul 11 '18 at 12:48
13

When you use the @Cacheable annotation, the code that implements the cache search is outside of your method. Therefore, the synchronized modifier does not affect it.

If you want all the threads to use the cached result, you should create a synchronized method that wraps the invocation to the cacheable getNextPlaying method. Something like this:

public synchronized List<Match> getNextPlayingSynchronized(Date startingFrom, Locale locale){
     return getNextPlaying(Date startingFrom, Locale locale);
}
...
@Cacheable(value="nextPlaying", key = "#startingFrom.getYear() + #startingFrom.getMonth() + #startingFrom.getDay() + #startingFrom.getHours() + #startingFrom.getMinutes() + #locale.getLanguage()")
public List<Match> getNextPlaying(Date startingFrom, Locale locale){
...//your old method without the synchronized modifier
}

It's important that these methods are in different classes. Otherwise, the aspects don't work.

Andres
  • 10,561
  • 4
  • 45
  • 63
  • Nice solution, but then It won't cache anything, never :S I can't understand why – ianaz Mar 06 '15 at 09:28
  • 5
    @ianaz. Put the methods con different clases. Otherwise, the aspects don't work. – Andres Mar 06 '15 at 09:48
  • 1
    This does not work because spring created a proxy class, as you call the @Cachable from inside the same class, it does not pass the proxy and therefor will not cache. a) put it in a different class b) use cglib c) use self reference (import your own class \\@Autowired MyClass self; then call self.getNextPlaying) – R.A Jan 16 '20 at 13:18