計算接口:
/**
* @author yaoqiang
* @create 2020-03-26 20:38
* @desc 模擬計算類型
**/
public interface Computable<A,V> {
V compute(A a) throws InterruptedException;
}
計算實現:
/**
* @author yaoqiang
* @create 2020-03-26 20:40
* @desc 計算實例
**/
public class ExpensiveFunction implements Computable<String, BigInteger> {
@Override
public BigInteger compute(String s) throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
return new BigInteger(s);
}
}
初步緩存實現:
直接給計算方法上面加鎖,這樣可以實現緩存的目的,但是計算變成了同步方法,只能由一個線程能夠計算,容易造成大量阻塞,有的時候甚至比不上不用緩存,這種方式過於保守
/**
* @author yaoqiang
* @create 2020-03-26 20:42
* @desc 記憶
**/
public class Memorizer<A,V> implements Computable<A,V> {
private final HashMap<A,V> cache = new HashMap<>();
private final Computable<A,V> computable;
public Memorizer(Computable<A, V> computable) {
this.computable = computable;
}
@Override
public synchronized V compute(A a) throws InterruptedException {
V v = cache.get(a);
if(v == null){
V result = computable.compute(a);
cache.put(a, result);
}
return v;
}
}
初步升級:
使用ConcurrentHashMap,具有更好的併發性能,但是存在兩個缺點
1. 如果計算需要花費大量時間,第一個線程正在計算,而第二個線程準備計算同樣的值,但是發現緩存裏面沒有,於是又要重複計算
2. 併發下的問題,如果兩個線程,同時返回null,同樣重複計算,重複添加,造成迷惑的結果
/**
* @author yaoqiang
* @create 2020-03-26 20:42
* @desc 記憶
**/
public class Memorizer<A,V> implements Computable<A,V> {
private final ConcurrentHashMap<A,V> cache = new ConcurrentHashMap<>();
private final Computable<A,V> computable;
public Memorizer(Computable<A, V> computable) {
this.computable = computable;
}
@Override
public V compute(A a) throws InterruptedException {
V v = cache.get(a);
if(v == null){
V result = computable.compute(a);
cache.put(a, result);
}
return v;
}
}
解決問題1:
放入一個準備future,放入map對象,如果有重複,通過future來獲取,
FutureTask有幾種狀態:等待運行,正在運行,運行完成,只要有狀態了,就不會在出現,因爲運算是按過長而產生的重複計算
/**
* @author yaoqiang
* @create 2020-03-26 20:42
* @desc 記憶
**/
public class Memorizer<A,V> implements Computable<A,V> {
private final ConcurrentHashMap<A,Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<A,V> computable;
public Memorizer(Computable<A, V> computable) {
this.computable = computable;
}
@Override
public V compute(A a) throws InterruptedException, ExecutionException {
Future<V> v = cache.get(a);
if(v == null){
Callable<V> callable = ()-> computable.compute(a);
FutureTask<V> futureTask = new FutureTask<>(callable);
v = futureTask;
cache.put(a,v);
futureTask.run();
}
return v.get();
}
}
解決問題2:
再次判斷,使用
putIfAbsent 來判斷是否需要運算,避免多線程導致的重複運算,清理很重要,這也式爲了保證緩存的準確性
/**
* @author yaoqiang
* @create 2020-03-26 20:42
* @desc 記憶
**/
public class Memorizer<A,V> implements Computable<A,V> {
private final ConcurrentHashMap<A,Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<A,V> computable;
public Memorizer(Computable<A, V> computable) {
this.computable = computable;
}
@Override
public V compute(A a) throws InterruptedException {
while (true) {
Future<V> v = cache.get(a);
if (v == null) {
Callable<V> callable = () -> computable.compute(a);
FutureTask<V> futureTask = new FutureTask<>(callable);
v = cache.putIfAbsent(a, futureTask);
if (v == null) {
v = futureTask;
futureTask.run();
}
}
try {
return v.get();
} catch (CancellationException e) {
cache.remove(a);
} catch (ExecutionException e) {
cache.remove(a);
}
}
}
}