一種線程安全的緩存工具實現(xiàn)方式
前言
在多線程環(huán)境下,緩存是一個常見的性能優(yōu)化手段。然而,實現(xiàn)一個線程安全的緩存并不容易,尤其是在高并發(fā)場景下,如何避免重復(fù)計算、保證數(shù)據(jù)一致性是一個挑戰(zhàn)。
最近在讀《Java并發(fā)編程實戰(zhàn)》時,書中提到了一種基于 ConcurrentHashMap 和 FutureTask 的線程安全緩存實現(xiàn)方式,今天就來分享記錄一下。
實現(xiàn)背景
在高并發(fā)場景中,緩存的核心作用是避免重復(fù)計算。比如,某個計算任務(wù)非常耗時,如果多個線程同時請求相同的數(shù)據(jù),我們希望只計算一次,后續(xù)請求直接使用緩存結(jié)果。
然而,實現(xiàn)這樣的緩存工具需要考慮以下幾個問題:
線程安全:多個線程可能同時訪問緩存,如何避免競態(tài)條件?
避免重復(fù)計算:如何確保相同的計算任務(wù)只執(zhí)行一次?
異常處理:如果計算任務(wù)拋出異常,如何清理緩存并通知調(diào)用方?
實現(xiàn)代碼
public interface Computable<A, V> {
V compute(A arg) throws InterruptedException;
}
import java.util.Map;
import java.util.concurrent.*;
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) {
this.computable = computable;
}
@Override
public V compute(A arg) throws InterruptedException {
while (true) {
Future<V> future = cache.get(arg);
if (future == null) {
Callable<V> eval = () -> computable.compute(arg);
FutureTask<V> futureTask = new FutureTask<>(eval);
future = cache.putIfAbsent(arg, futureTask);
if (future == null) {
future = futureTask;
futureTask.run();
}
}
try {
return future.get();
} catch (CancellationException e) {
// 如果任務(wù)被取消,移除緩存中的 Future
cache.remove(arg, future);
} catch (ExecutionException e) {
// 如果計算任務(wù)拋出異常,移除緩存中的 Future 并拋出異常
cache.remove(arg, future);
throw new RuntimeException("Computation failed for argument: " + arg, e.getCause());
}
}
}
}
實現(xiàn)思路說明
使用 ConcurrentHashMap 中 putIfAbsent 方法來保證,同一時刻只能有一個線程可以將 FutureTask 放入緩存,從而避免相同任務(wù)的重復(fù)計算;當(dāng)計算任務(wù)發(fā)生異常時,可以及時拋出錯誤/取消緩存結(jié)果。
應(yīng)用場景
假設(shè)我們有一個 CPU 重負(fù)載的函數(shù) slow(x),但它的結(jié)果是穩(wěn)定的。換句話說,對于相同的 x,它總是返回相同的結(jié)果。
如果經(jīng)常調(diào)用該函數(shù),我們可能希望將結(jié)果緩存(記住)下來,以避免在重新計算上花費額外的時間。
- 該算法沒有緩存失效機(jī)制,所以只適用于參數(shù)固定,結(jié)果固定的場景。
- 算法使用的 FutureTask 異步機(jī)制,適用于計算結(jié)果耗時的操作。
下面舉一個,計算階乘的例子(可以使用備忘錄進(jìn)行優(yōu)化,此處為了效果直接進(jìn)行計算)
public class Calculate implements Computable<Integer, Integer> {
@Override
public Integer compute(Integer arg) {
// 模擬復(fù)雜計算情況
if (arg == 0 || arg == 1) {
return 1;
}
return arg * compute(arg - 1);
}
}
public class TestApp {
public static void main(String[] args) throws InterruptedException {
CacheUtils<Integer, Integer> cacheUtils = new CacheUtils<>(new Calculate());
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Integer temp1 = cacheUtils.compute(19);
stopWatch.stop();
System.out.printf("首次計算:耗時%s 結(jié)果%s%n", stopWatch.getLastTaskTimeMillis(), temp1);
stopWatch.start();
Integer temp2 = cacheUtils.compute(19);
stopWatch.stop();
System.out.printf("命中緩存:耗時%s 結(jié)果%s%n", stopWatch.getLastTaskTimeMillis(), temp2);
}
}
本文來自博客園,作者:帥氣的濤啊,轉(zhuǎn)載請注明原文鏈接:http://www.rzrgm.cn/handsometaoa/p/18682740

浙公網(wǎng)安備 33010602011771號