为计算机结果建立高效,可伸缩的的高速缓存(java并发编程实践读书笔记四)


  • 复用已有的计算结果,缩短等待时间,提高吞吐量,代价是占用更多的内存
  • 经典思路:使用一个map,每次计算先在map中查找,找不到的情况下进行计算,把计算结果保存到map中,以便下次计算相同值时直接从map中取得计算结果
/**
* 这里定义一个Computable接口,实际的计算类和使用的缓存的计算类都实现该接口
* 使用的时候可以方便的切换是否使用缓存
*
* @param <A>
* @param <V>
*/
public interface Computable<A, V> {
    V computer(A arg) throws Exception;
}
 
 
import java.math.BigInteger;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* 实现了Computable接口的计算类,这里模拟了一个复杂耗时的计算
*
*/
class ExpensiveFunction implements Computable<String, BigInteger>{
    @Override
    public BigInteger computer(String arg) throws Exception {
        //after deep thought
        //...
        return new BigInteger(arg);
    }
}
/**
*使用了缓存的计算类
*
* @param <A>
* @param <V>
*/
public class Memoizer<A,V> implements Computable<A,V> {
    //使用并发容器ConcurrentHashMap,增强并发性
    //key是计算因子,计算结果使用进行异步调用的Future储存,Future代表了一个计算过程
    //计算的时候,先在cache中找该计算过程,获得计算结果;如果cache中不存在该计算过程,则新建一个计算过程,进行计算。
    //在新计算过程 开始计算--得到结果 之前,就把这个计算过程给缓存了,这时如果有多个计算请求arg(计算因子)都是相同的,
    //则返回给他们这个计算过程,让其阻塞等待结果,最终实现了每个arg(计算因子)只计算一次。
    private final ConcurrentHashMap<A,Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();
    private final Computable<A,V> c ;//实际进行计算的类
    public Memoizer(Computable<A,V> c){
        this.c=c; //把实际计算类的对象传进来
    }
    @Override
    public V computer(final A arg) throws Exception {
        while(true){
            Future<V> f = cache.get(arg);//直接获取该计算过程
            if(f==null){//如果计算过程不存在
                //通过Callable接口new一个FutureTask(计算过程,实现了Future接口)
                Callable<V> eval = new Callable<V>() {
                    @Override
                    public V call() throws Exception {
                        return c.computer(arg);
                    }
                };
                FutureTask<V> ft = new FutureTask<V>(eval);
                //cache.putIfAbsent(arg, ft)
                //方法:如果arg和ft在ConcurrentHashMap中已建立对应关系,则返回cache.get(arg);
                //如果没有建立关系,则简历关系,并且返回null
                f=cache.putIfAbsent(arg, ft);
                if(f==null){//当缓存中不存在此计算过程
                    f=ft;//把new出来的FutureTask赋值给f,下面调用f.get()的时候得到的是当前new出来的FutureTask的计算结果
                    ft.run();//开启计算过程
                }
            }
            try{
                //如果计算过程已经存在了,则直接调用FutureTask的阻塞方法get()
                //该方法会一直阻塞到计算出结果
                return f.get();
            }catch(CancellationException e){
                cache.remove(arg);//如果计算被取消了,则把缓存中的计算过程移除
            }catch(ExecutionException e){
                //调用方法处理其他的异常,这里的处理方法在之前的文章Synchronizer...中有提到
                throw launderThrowable(e.getCause());
            }
        }
    }
 
    //通过launderThrowable()方法处理其他情况的异常1.如果cause是一个Error,抛出;2.如果是一个RuntimeException,返回该异常;3.其他情况下抛出IllegalStateException
    //暂时我也不清楚为什么要这么处理,把Exception return出来有什么用么?还是要回头看java基础啊~~
    private Exception launderThrowable(Throwable cause) {
        if(cause instanceof Error)
            throw new Error(cause);
        else if(cause instanceof RuntimeException)
            return (Exception) cause;
        else throw new IllegalStateException(cause);
    }
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值