首相创建一下两个辅助类(接口)
Computable接口
public interface Computable<A, V> {
V compute(A arg) throws InterruptedException;
}
**ExpensiveFunction **(表示结果的计算过程)
public class ExpensiveFunction implements Computable<String, BigInteger> {
@Override
public BigInteger compute(String arg) throws InterruptedException {
return new BigInteger(arg);
}
}
通过HashMap实现缓存
如果存在则返回结果的值,否则先缓存再进行计算。因为HashMap不是线程安全的,所以采用了对整个方法进行同步,每次只有一个方法执行,多线程时,一个线程执行,其他都要阻塞,严重影响效率,可能还不如不使用缓存的程序。
public class Memoizer1<A, V> implements Computable<A, V> {
private final Map<A, V> cache = new HashMap<>();
private final Computable<A, V> computable;
public Memoizerl(Computable<A, V> computable) {
this.computable = computable;
}
@Override
public synchronized V compute(A arg) throws InterruptedException {
V result = cache.get(arg);
if(result == null){
result = computable.compute(arg);
cache.put(arg, result);
}
return result;
}
}
ConcurrentHashMap实现
弊端:如果某个线程启动了一个开销很大的计算,而其他线程不知道,那么就会重复这个计算。
public class Memoizer2<A, V> implements Computable<A, V> {
private final Map<A, V> cache = new ConcurrentHashMap<>();
private final Computable<A, V> computable;
public Memoizer2(Computable<A, V> computable) {
this.computable = computable;
}
@Override
public V compute(A arg) throws InterruptedException {
V result = cache.get(arg);
if(result == null){
result = computable.compute(arg);
cache.put(arg, result);
}
return result;
}
}
基于FutureTask实现
解决了上面的问题。FutureTask表示一个计算过程,这个过程可能已经计算完成,也可能正在进行。如果完成有结果,FutureTask.get()会立即返回,否则一直阻塞,直到计算结果在返回
代码如下:
public class Memoizer3<A, V> implements Computable<A, V> {
private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<A, V> computable;
public Memoizer3(Computable<A, V> computable) {
this.computable = computable;
}
@Override
public V compute(A arg) throws InterruptedException {
Future<V> futureTask = cache.get(arg);
if(futureTask == null){
Callable<V> eval = new Callable<V>() {
@Override
public V call() throws Exception {
return computable.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<>(eval);
futureTask = ft;
cache.put(arg, ft);
ft.run();
}
try{
return futureTask.get();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}
}
上述代码实现几乎是完美的,表现良好的并发性(基于ConcurrentHashMap高效的并发性),但仍然存在两个线程计算相同值的缺陷,但是概率很小。因为compute方法的if{}代码块的非原子的先检查再执行,所以仍然存在上述可能。根本原因在于复合操作在底层Map对象上执行,而这个对象无法通过加锁来实现原子性,但可以使用ConcurrentMap中的原子方法putIfAbsent来避免如下20行所示。
public class Memoizer<A, V> implements Computable<A, V> {
private final Map<A, Future<V>> cache = new ConcurrentHashMap<>();
private final Computable<A, V> computable;
public Memoizer(Computable<A, V> computable) {
this.computable = computable;
}
@Override
public V compute(A arg) throws InterruptedException {
Future<V> futureTask = cache.get(arg);
if(futureTask == null){
Callable<V> eval = new Callable<V>() {
@Override
public V call() throws Exception {
return computable.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<>(eval);
//原子操作
futureTask =cache.putIfAbsent(arg, ft);
if(futureTask == null) {
futureTask = ft;
ft.run();
}
}
try{
return futureTask.get();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}
}