构建高效的结果缓存

场景:假设某个计算过程需要耗费大量的时间,为了提高用户体验,希望将计算结果存入缓存,每次web端请求尝试从缓存中直接获取计算结果,如果缓存中没有,则计算并存入。

问题:这个需求的一大问题是,假设第一次请求发生后,程序正在运行计算函数,在未返回结果前,第二次请求发起,因为缓存中暂时没有存入第一次请求的计算结果,所以第二次请求依然会执行,因此必须加上同步策略。效率最低的解决方案是在获取缓存结果或计算的方法上加上synchronized关键字,但这意味着同一时刻仅有一个线程可以执行该方法。本案例旨在构建高效的结果缓存。


一、定义通用计算接口

/**
 * description: 计算接口 <br>
 */
public interface Computable<A, V> {
    
    V compute(A arg) throws InterruptedException;
}

二、高效的结果缓存类

/**
 * description: 线程安全的缓存类 <br>
 * 尚缺乏解决缓存逾期或缓存清理的功能
 */
public class Memoizer<A, V> implements Computable<A, V> {

    private final Computable<A, V> c;
    
    // 使用Future避免多个线程同时进行重复消费
    // 缓存Map的value由结果值 V 改为 Future<V>,这样判断任务是否存在即可,而无需计算完成
    private final ConcurrentMap<A, Future<V>> cache = new ConcurrentHashMap<A, Future<V>>();

    public Memoizer(Computable<A, V> c) {
        this.c = c;
    }

    @Override
    public V compute(A arg) throws InterruptedException {
        while (true) {
            Future<V> f = cache.get(arg);
            if (f == null) {
                Callable<V> eval = new Callable<V>() {
                    @Override
                    public V call() throws InterruptedException {
                        return c.compute(arg);
                    }
                };
                FutureTask<V> ft = new FutureTask<V>(eval);
                // 使用ConcurrentMap的putIfAbsent方法避免两个以上的线程同时进行重复的计算
                f = cache.putIfAbsent(arg, ft);
                if (f == null) {
                    f = ft;
                    ft.run();
                }
            }
            try {
                return f.get();
            } catch (CancellationException e) {//若计算取消,将Future从缓存中移除,这样将来的计算才可能成功
                cache.remove(arg, f);
            } catch (ExecutionException e) {
                throw ExceptionUtil.launderThrowable(e.getCause());
            }
        }
    }
}

三、客户端示例

/**
 * description: 使用缓存的客户端 <br>
 */
public class Factorizer implements Servlet {
    
    private final Computable<BigInteger, BigInteger[]> c = arg -> factor(arg);

    private final Computable<BigInteger, BigInteger[]> cache = new Memoizer<>(c);

    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        try {
            BigInteger i = extractFromRequest(req);
            encodeIntoResponse(req, cache.compute(i));
        } catch (InterruptedException e) {
            encodeError(resp, "factorization interrupted");
        }
    }

    /**
     * 执行因式分解的操作
     * @param arg
     * @return
     */
    private BigInteger[] factor(BigInteger arg) {
        return new BigInteger[0];
    }
}

参考文献

《Java并发编程实战》第五章 5.6构建高效且可伸缩的结果缓存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值