计算接口:
/**
* @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);
}
}
}
}