How does Spring Boot's @Cacheable handle cache eviction during concurrent method executions?
Question
How does Spring Boot’s @Cacheable handle cache eviction during concurrent method executions?
Answer
When multiple threads invoke a cache-evicting method simultaneously, Spring’s default @CacheEvict does not inherently prevent race conditions, potentially leading to cache stampede or stale data writes. By default, the eviction occurs after the method execution completes, meaning concurrent threads might read stale cache entries before the eviction propagates.
To handle this safely, you must decouple the cache eviction from the method execution or use explicit synchronization. A robust approach involves using a ReentrantLock to ensure that only one thread performs the heavy computation and subsequent cache invalidation at a time, preventing concurrent threads from stepping on each other’s toes.
Consider the following production-grade implementation using a ReentrantLock to guarantee strict data consistency:
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class ConcurrentCacheService {
private final ReentrantLock lock = new ReentrantLock();
@Cacheable(value = "data", key = "#id")
public String fetchData(String id) {
return expensiveDatabaseCall(id);
}
@CacheEvict(value = "data", key = "#id", beforeInvocation = true)
public String updateData(String id, String newValue) {
lock.lock();
try {
// Perform update logic
return saveToDatabase(id, newValue);
} finally {
lock.unlock();
}
}
private String expensiveDatabaseCall(String id) {
// Simulated heavy query
return "Data-" + id;
}
private String saveToDatabase(String id, String newValue) {
// Simulated heavy write
return newValue;
}
}
Using beforeInvocation = true on @CacheEvict ensures the cache is cleared before the database write begins, preventing other threads from reading stale data during the update window. However, this alone does not stop concurrent threads from entering the method simultaneously. The ReentrantLock guarantees that only one thread executes the critical section, effectively eliminating the race condition. The tradeoff here is increased latency for concurrent requests attempting to acquire the lock, but it ensures strict data consistency across the cache and the underlying datastore.
📲 Practice Offline on Mobile: Download the free QA Automation & SDET Prep app on Google Play & App Store.