一头扎进多线程-构建高效且可伸缩的结果缓存

来源:互联网 发布:相叶雅纪水川麻美 知乎 编辑:程序博客网 时间:2024/04/29 16:11

通过多线程的所有组件学习之后,我们要学习到如何把一个类包装成一个多线程安全类,下面通过构造一个——计算缓存类,在构造的过程中一步一步的优化,最终来得到我们想要的计算缓存类。

类提供的功能:类提供一个计算的功能,然后把计算传入的值与结果缓存在一个Map中,当第二次计算时先从缓存里面查看看曾经有没有计算过,有的话就直接返回结果,没有的话就进行计算,存到缓存再返回结果。

菜鸟的做法

如果是我的话,首先我会这么做

定义一个计算的接口,里面提供一个计算的方法,等待实现

public interface Computable<A,V> {//V代表返回结果    V computer(A arg);//A表示参数}

定义一个具体的计算类,用来实现计算的功能

public class ExpensiveFunction implements Computable<String,BigInteger> {    /* (非 Javadoc)     * @see com.jjt.cache.Computable#computer(java.lang.Object)     */    @Override    public BigInteger computer(String arg) {        //模拟经过长时间的计算        return new BigInteger(arg);    }}

最后定义一个主类来实现我们想要的逻辑,这里使用的是组合的方式来进行计算

public class Memosizer<A,V> implements Computable<A, V>{    private final Map<A,V> cache = new HashMap<A,V>();//缓存数据结构    private final Computable<A,V> c;//具体计算类    /**     *      */    public Memosizer(Computable<A, V> c) {        this.c=c;//构造函数传入一个具体的计算类    }    /* (非 Javadoc)     * @see com.jjt.cache.Computable#computer(java.lang.Object)     */    @Override    public synchronized V computer(A arg) {//实现我们想要的逻辑            V result = cache.get(arg);            if(result==null){                result = c.computer(arg);                cache.put(arg, result);            }            return result;    }}

解析上面的代码,我们可以看到,由于hashMap是非线程安全的,所以我们只能通过synchronized 加一个隐形锁来对我们的逻辑进行锁定,但是这样导致我们发生并发时,我们会因为计算时间的关系导致线程间进行长时间的等待,降低了吞吐率。那我们接下来看看如何改进。

把线程安全性交给线程安全Map类管理

//  private final Map<A,V> cache = new HashMap<A,V>();    private final Map<A,V> cache = new ConcurrentHashMap<A,V>();

ConcurrentHashMap内部实现机构是分段锁,去除隐式锁换成并发Map有助于我们增加吞吐量,不过这里又存在另外的一个问题。
这里写图片描述

延时任务交给FutureTask

我们已经知道有一个类能基本实现这个功能:FutureTask。FutureTask表示一个计算的过程。这个过程可能是已经计算完成,也可能是正在进行。如果结果可用,那么 FutureTask.get将立即返回,否则会一直阻塞,直到结果计算出来再将其返回。
所以我们现在把原来的

ConcurrenHashMap<A,V>,更改为ConcurrentHashMap<A,Future<V>>

我们直接把计算的步骤放到FutureTask里面,让他帮我们进行处理,我们直接把FutureTask放到缓存里面,需要的时候再get出来,这样就可以解决了我们第二个点提到的那个技术难题。改进后的Memosize如下:

public class Memosizer<A,V> implements Computable<A, V>{//  private final Map<A,V> cache = new HashMap<A,V>();    private final Map<A,Future<V>> cache = new ConcurrentHashMap<A,Future<V>>();    private final Computable<A,V> c;//具体计算类    /**     *      */    public Memosizer(Computable<A, V> c) {        this.c=c;    }    /* (非 Javadoc)     * @see com.jjt.cache.Computable#computer(java.lang.Object)     */    @Override    public  V computer(A arg) throws InterruptedException, ExecutionException {//去除分段锁之后            Future<V> result = cache.get(arg);            if(result==null){                Callable<V> callable = new Callable<V>() {                    @Override                    public V call() throws Exception {                        return c.computer(arg);                    }                       };                FutureTask<V> f = new FutureTask<V>(callable);                result=f;                cache.put(arg, result);                f.run();//这里将调用c.compute方法,在这里线程被阻塞等没关系,因为futureTask已经进了缓存了            }            return result.get();    }}

虽然上面的代码看起来好像几乎是完美的,但是不要忘了还存在一个非原子操作
这里写图片描述

高手最后的进阶

这里写图片描述

1 0
原创粉丝点击