Preface
In a multi-threaded environment, caching is a common performance optimization method. However, it is not easy to implement a thread-safe cache, especially in high-concurrency scenarios. How to avoid repeated calculations and ensure data consistency is a challenge.
Recently, when I was reading "Java Concurrent Programming in Practice", the book mentioned a thread-safe cache implementation method based on ConcurrentHashMap and FutureTask. I will share and record it today.
Implementation background
In high-concurrency scenarios, the core role of cache is to avoid repeated calculations. For example, a certain calculation task is very time-consuming. If multiple threads request the same data at the same time, we hope to only calculate it once and use the cached results directly for subsequent requests.
However, implementing such a caching tool needs to consider the following issues:
Thread safety: Multiple threads may access the cache at the same time, how to avoid race conditions?
Avoid double calculations: How to ensure that the same calculation task is only performed once?
Exception handling: If a computing task throws an exception, how to clean the cache and notify the caller?
Implement code
public interface Computable<A, V> {
V compute(A arg) throws InterruptedException;
}
import ;
import .*;
public class CacheUtils<A, V> implements Computable<A, V> {
private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<A, V> computable;
public CacheUtils(Computable<A, V> computable) {
= computable;
}
@Override
public V compute(A arg) throws InterruptedException {
while (true) {
Future<V> future = (arg);
if (future == null) {
Callable<V> eval = () -> (arg);
FutureTask<V> futureTask = new FutureTask<>(eval);
future = (arg, futureTask);
if (future == null) {
future = futureTask;
();
}
}
try {
return ();
} catch (CancellationException e) {
// If the task is canceled, remove the Future from the cache
(arg, future);
} catch (ExecutionException e) {
// If the calculation task throws an exception, remove the Future in the cache and throw the exception
(arg, future);
throw new RuntimeException("Computation failed for argument: " + arg, ());
}
}
}
}
Implementation idea description
useConcurrentHashMap
middleputIfAbsent
method to ensure that only one thread can transfer theFutureTask
Put it into the cache to avoid repeated calculations of the same task; when an exception occurs in the calculation task, an error can be thrown immediately/the cached result can be cancelled.