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

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

类提供的功能:类提供一个计算的功能,然后把计算传入的值与结果缓存在一个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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值