• 设计并实现一个并发安全的LRU(Least Recently Used,最近最少使用)缓存结构


    前言

    相信很多人都使用过LinkedHashMap,LinkedHashMap中的removeEldestEntry可以删除老旧的元素,我们可以以此来实现一个LRU缓存结构,并结合java中JUC包中的各种多线程锁机制来保证多线程安全。

    以下是我遇见过的一个高级面试题:

    【面试题】设计并实现一个并发安全的LRU(Least Recently Used,最近最少使用)缓存结构。要求支持以下操作:
    get(key):如果键存在于缓存中,则获取其对应的值(总是返回最新添加的值),否则返回-1。
    put(key, value):如果键已经存在,则更新其对应的值;如果键不存在,并且缓存未满,则添加该键值对到缓存中;如果缓存已满,则移除最近最少使用的键值对,然后再将新的键值对添加到缓存。

    在这里插入图片描述

    实战演示

    可以采用LinkedHashMap的removeEldestEntry方法删除需要淘汰的元素,并直接使用map的get/put方法。

    模拟代码

    import java.util.LinkedHashMap;
    import java.util.Map;
    
    
    /**
     * LRUCache
     * @author senfel
     * @date 2024/2/26 12:50
     */
    public class LRUCache<K, V> {
        private final int capacity;
        private LinkedHashMap<K, V> cache;
    
        public LRUCache(int capacity) {
            this.capacity = capacity;
            // 设置访问顺序和插入顺序一致,且当容量达到阈值时自动删除最近最少使用的项
            this.cache = new LinkedHashMap<K, V>(capacity, 0.75f, true) {
                @Override
                protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                    return size() > capacity;
                }
            };
        }
    
        /**
         * get
         * @author senfel
         * @date 2024/2/26 12:52
         * @return V 
         */
        public V get(K key) {
            synchronized (cache) {
                return cache.getOrDefault(key, null);
            }
        }
        /**
         * put
         * @author senfel
         * @date 2024/2/26 12:52
         * @return void
         */
        public void put(K key, V value) {
            synchronized (cache) {
                cache.put(key, value);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    上述代码实现了一个基本的LRU缓存结构,但get方法在key不存在时返回null而不是-1,并且没有完全解决并发访问问题。而且我们在获取到已经存在的是数据后并没有刷新数据,也就是并没有实现醉经最少使用的淘汰策略。

    下面是更完善的版本,包含正确的get方法逻辑以及线程安全的put和get操作,以及在获取到数据后将数据重新刷新了。

    优化后的代码

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.LinkedHashMap;
    import java.util.Map;
    /**
     * LRUCache
     * @author senfel
     * @date 2024/2/26 12:55
     */
    public class LRUCache<K, V> {
        private final int capacity;
        private final LinkedHashMap<K, V> cache;
        private final Lock lock = new ReentrantLock();
    
        public LRUCache(int capacity) {
            this.capacity = capacity;
            this.cache = new LinkedHashMap<K, V>(capacity + 1, 0.75f, true) {
                @Override
                protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                    return size() > capacity;
                }
            };
        }
        /**
         * get
         * @author senfel
         * @date 2024/2/26 12:58
         * @return V 
         */
        public V get(K key) {
            lock.lock();
            try {
                V value = cache.get(key);
                if (value != null) {
                    // 触发访问,使得此条目变为最近使用过的
                    cache.remove(key);
                    cache.put(key, value);
                }
                return value;
            } finally {
                lock.unlock();
            }
        }
        /**
         * get
         * @author senfel
         * @date 2024/2/26 12:58
         * @return void
         */
        public void put(K key, V value) {
            lock.lock();
            try {
                // 首先尝试移除旧的键值对,即使它可能不存在
                cache.remove(key);
                cache.put(key, value);
            } finally {
                lock.unlock();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    写在最后

    在这个高级版本的LRU缓存实现中,我们使用了LinkedHashMap作为基础数据结构,并通过重写removeEldestEntry方法实现了缓存满时自动淘汰最久未使用的元素。同时,为了保证在多线程环境下的线程安全性,我们在get和put方法上加了synchronized关键字或者使用了ReentrantLock来确保同一时间只有一个线程能执行修改缓存的操作。在get方法中,我们还额外做了一步,即在获取到key对应值后重新插入以更新其为最近使用的元素。

  • 相关阅读:
    把握出租车行驶的数据脉搏 :出租车轨迹数据给你答案!
    运算放大器学习笔记
    基于springboot+vue的家政服务预约管理系统 elementui
    冠状病毒疾病优化算法 (COVIDOA)附matlab代码
    Kafka 集群实现数据同步
    Fast——Nginx
    MySQL事务与存储引擎相关设置
    OSI七层网络模型、TCP三次握手四次分手及连接拒绝
    Java学习之包访问修饰符
    如何使用python获取ssl证书信息
  • 原文地址:https://blog.csdn.net/weixin_39970883/article/details/136299165