设计并实现一个并发安全的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方法。

模拟代码

bash 复制代码
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);
        }
    }
}

上述代码实现了一个基本的LRU缓存结构,但get方法在key不存在时返回null而不是-1,并且没有完全解决并发访问问题。而且我们在获取到已经存在的是数据后并没有刷新数据,也就是并没有实现醉经最少使用的淘汰策略。
下面是更完善的版本,包含正确的get方法逻辑以及线程安全的put和get操作,以及在获取到数据后将数据重新刷新了。

优化后的代码

bash 复制代码
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();
        }
    }
}

写在最后

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

相关推荐
NiceCloud喜云3 分钟前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
jiayong2338 分钟前
面试中遇到不熟悉问题的应对策略深度解析
面试·职场和发展
_日拱一卒1 小时前
LeetCode:994腐烂的橘子
java·数据结构·算法·leetcode·深度优先
隔窗听雨眠1 小时前
Nginx网关响应慢排查手记
java·服务器·nginx
智慧物业老杨2 小时前
智慧物业合同周期管理系统:从风险预警到智能交接的全流程数智化落地方案
java·人工智能·python
源码宝2 小时前
MES系统源码:Java8 + SpringBoot2.7 + MySQL8 + Redis,后端源码清爽易扩展
java·后端·源码·springboot·mes系统·源码二开·mes源码
JAVA社区2 小时前
Java高级全套教程(十)—— SpringCloudAlibaba超详细实战详解
java·开发语言·spring cloud·面试·职场和发展
金銀銅鐵2 小时前
[Java] 如何理解 class 文件中方法的 descriptor?
java·后端
云烟成雨TD3 小时前
Spring AI Alibaba 1.x 系列【63】AI Agent 长期记忆
java·人工智能·spring
憧憬成为java架构高手的小白3 小时前
苍穹外卖--day09
java·spring boot·百度