LocalCache实现原理及例题讲解

使用场景

Java应用中,对于访问频率高,更新少的数据,通常的方案是将这类数据加入缓存中。相对从数据库中读取来说,读缓存效率会有很大提升。

在集群环境下,常用的分布式缓存有RedisMemcached等。但在某些业务场景上,可能不需要去搭建一套复杂的分布式缓存系统,在单机环境下,通常是会希望使用内部的缓存(LocalCache)。

实现

这里提供了两种LocalCache的实现,一种是基于ConcurrentHashMap实现基本本地缓存,另外一种是基于LinkedHashMap实现LRU策略的本地缓存。

基于ConcurrentHashMap的实现
    static {
        timer = new Timer();
        map = new ConcurrentHashMap<>();
    }

ConcurrentHashMap作为缓存的存储结构。因为ConcurrentHashMap的线程安全的,所以基于此实现的LocalCache在多线程并发环境的操作是安全的。在JDK1.8中,ConcurrentHashMap是支持完全并发读,这对本地缓存的效率也是一种提升。通过调用ConcurrentHashMapmap的操作来实现对缓存的操作。

私有构造函数
    private LocalCache() {

    }

LocalCache是工具类,通过私有构造函数强化不可实例化的能力。

缓存清除机制
   /**
     * 清除缓存任务类
     */
    static class CleanWorkerTask extends TimerTask {

        private String key;

        public CleanWorkerTask(String key) {
            this.key = key;
        }

        public void run() {
            LocalCache.remove(key);
        }
    }

清理失效缓存是由Timer类实现的。内部类CleanWorkerTask继承于TimerTask用户清除缓存。每当新增一个元素的时候,都会调用timer.schedule加载清除缓存的任务。

基于LinkedHashMap的实现

LinkedHashMap作为缓存的存储结构。主要是通过LinkedHashMap的按照访问顺序的特性来实现LRU策略。

LRU

LRULeast Recently Used的缩写,即最近最久未使用。LRU缓存将会利用这个算法来淘汰缓存中老的数据元素,从而优化内存空间。

基于LRU策略的map

这里利用LinkedHashMap来实现基于LRU策略的map。通过调用父类LinkedHashMap的构造函数来实例化map。参数accessOrder设置为true保证其可以实现LRU策略。

static class LRUMap<K, V> extends LinkedHashMap<K, V> {

        ...  // 省略部分代码
        
        public LRUMap(int initialCapacity, float loadFactor) {
            super(initialCapacity, loadFactor, true);
        }

        ... // 省略部分代码
        
        /**
         * 重写LinkedHashMap中removeEldestEntry方法;
         * 新增元素的时候,会判断当前map大小是否超过DEFAULT_MAX_CAPACITY,超过则移除map中最老的节点;
         *
         * @param eldest
         * @return
         */
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > DEFAULT_MAX_CAPACITY;
        }

    }
线程安全
        /**
         * 读写锁
         */
        private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

        private final Lock rLock = readWriteLock.readLock();

        private final Lock wLock = readWriteLock.writeLock();

LinkedHashMap并不是线程安全,如果不加控制的在多线程环境下使用的话,会有问题。所以在LRUMap中引入了ReentrantReadWriteLock读写锁,来控制并发问题。

缓存淘汰机制
        protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
            return size() > DEFAULT_MAX_CAPACITY;
        }

此处重写LinkedHashMapremoveEldestEntry方法, 当缓存新增元素的时候,会判断当前map大小是否超过DEFAULT_MAX_CAPACITY,超过则移除map中最老的节点。

缓存清除机制

缓存清除机制与ConcurrentHashMap的实现一致,均是通过timer实现。



作者:特立独行的猪手
链接:https://www.jianshu.com/p/4194483127fe
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


---------------------------------------------------------------------------------------------------------------------------

LRU原理:

LRU一般采用链表的放缓死实现,便于快速移动数据位置。网上找了一个图,感觉画的很赞,所以就粘过来了,为了说明问题~~~~~感谢这个图的作者!!!!


LRU的实现有两种方式:缓存命中后,是否要这个数据缓存项到LRU队列的最前端。

首先这个图说明的是LRU的一种实现方式,那就是在缓存访问命中后,要将这个数据缓存项到达LRU队列的最前端。第五步,将E插入缓存池后,这个缓存池已经满了,所以第六步插入F后,要考虑把近期没有访问的数据,也就是A给淘汰掉了(可怜的A啊。。。。)第七步,C被访问,从时间点来说,C最近被访问过了,所以移动到了链表的头部(暂时没有被淘汰);第八步,将G存入缓存后,G位于链表头部,那么B只能被淘汰了。。。。

因为链表的插入和删除时间复杂度都为O(1),所以用链表不用数组。。。。。(这句是参考别人的,还不是很懂,让我研究研究。。。)

还有数据缓存项不移动到LRU最前端的方式,由下面的例题来详细说明。

下面贴一个例题:

—————————————————————————————————————————————————————————————————————————————

解:

本题是有两种解答的。

1.缓存命中后,这个数据缓存项不要移动到链表最前端:

命中不调整缓存项内容快照如下:


1.  1

2.  5 1

3.  5 1

4.  3 5 1

5.  3 5 1

6.  2 3 5 1

7.  4 2 3 5(淘汰1)

8.  1 4 2 3 (淘汰5)

9   1 4 2 3(命中2

到达第6步时,缓存池已经满了,所以开始淘汰,淘汰两次,最后淘汰5

2.缓存命中后,这个数据缓存项要移动到链表最前端

1. 1

2. 5 1

3. 1 5

4. 3 1 5

5 .5 3 1

6. 2 5 3 1

7. 4 2 5 3(淘汰1)

8. 1 4 2 5(淘汰3)

9 .2 1  4 5

淘汰2次,最后淘汰3


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值