缓存实现思路

Posted jinshuai86

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了缓存实现思路相关的知识,希望对你有一定的参考价值。

缓存主要为了解决各个组件之间读取速度不匹配问题,比如寄存器是L1的缓存,L1是L2的缓存,L2是L3的缓存,L3是内存的缓存等。通过读Java Concurrency Practice P85,实现了一个简单可以添加和获取数据的缓存。其它的诸如缓存过期,更新缓存等没有实现- -!!

代码

计算接口,用到了装饰者模式。


public interface Computable<A,V> {
    V compute(A arg);
}

一种计算的实现

public class ExpensiveFunction implements Computable<String,BigInteger> {

    @Override
    public BigInteger compute(String arg) {
       // 经过长时间计算后
        try {
            Thread.sleep(500000);           
        } catch (InterruptedException ie) {

        }
        return new BigInteger(arg); 
    }

}

版本1

通过HashMap时间复杂度为O(1)的特性以及synchronized保证线程安全来构建缓存


public class MyCacheV1<A,V> implements Computable<A,V>{

    private Map<A,V> cache = new HashMap<>();
    private Computable computable;

    public MyCacheV1(Computable computable) {
        this.computable = computable;
    }

    @Override
    public synchronized V compute(A arg) {
        V result = cache.get(arg);
        if (result == null) {
            result = (V)computable.compute(arg);
            cache.put(arg,result);
        }
        return result;
    }

}

版本2

版本1添加了synchronized,多线程情况下造成性能下降 -> 换成ConcurrentHashMap

public class MyCacheV2<A,V> implements Computable<A,V>{

    private Map<A,V> cache = new ConcurrentHashMap<>();
    private Computable computable;

    public MyCacheV2(Computable computable) {
        this.computable = computable;
    }

    @Override
    public V compute(A arg) {
        V result = cache.get(arg);
        if (result == null) {
            result = (V)computable.compute(arg);
            cache.put(arg,result);
        }
        return result;
    }

}

版本3

版本2先判断 -> 在计算属于复合操作而且没有加锁导致线程不安全会产生重读计算。如果遇到计算时间非常长的计算,一旦重复会消耗大量的资源。
解决思路:如果其他线程正在计算目标值,当前线程阻塞直到其它线程计算出结果返回即可。
实现:通过FutureTask的get()方法。如果没有结果,会阻塞当前线程。

public class MyCacheV3<A,V> implements Computable<A,V>{

    private Map<A,FutureTask> cache = new ConcurrentHashMap<>();
    private Computable computable;

    public MyCacheV3(Computable computable) {
        this.computable = computable;
    }

    @Override
    public V compute(A arg) {
        Future future = cache.get(arg);
        if (future == null) {
            FutureTask futureTask = new FutureTask(new Callable() {
                @Override
                public V call() throws Exception {
                    return (V)computable.compute(arg);
                }
            });
            // 用到了ConcurrentHashMap的原子操作
            future = cache.putIfAbsent(arg,futureTask);
            // 二次判断
            if (future == null) {future = futureTask; futureTask.run();}
        }
        V result = null;
        try {
            result = (V) future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return result;
    }

}


以上是关于缓存实现思路的主要内容,如果未能解决你的问题,请参考以下文章

Android主流视频播放及缓存实现原理调研

Android获取各个应用程序的缓存文件代码小片段(使用AIDL)

什么是缓存穿透缓存雪崩 及 解决缓存穿透雪崩解决方案实现

片段(Java) | 机试题+算法思路+考点+代码解析 2023

多图片下载缓存及SDWebImage

Android 高级UI解密 :PathMeasure截取片段 与 切线(新思路实现轨迹变换)