要用 linkedhashmap 实现真正可用的轻量级 lru 缓存,关键不是继承后随便重写一个方法,而是把三个动作做准:开启访问顺序、精准控制淘汰逻辑、避开高频变量场景下的典型陷阱。

要用 LinkedHashMap 实现真正可用的轻量级 LRU 缓存,关键不是继承后随便重写一个方法,而是把三个动作做准:开启访问顺序、精准控制淘汰逻辑、避开高频变量场景下的典型陷阱。它适合单线程或低并发下读多写少的热点数据,比如用户会话 ID、模板参数、限流令牌等,不推荐用于高并发或带过期时间的复杂场景。
必须显式启用 accessOrder = true
LinkedHashMap 默认是插入顺序(accessOrder = false),此时 get() 不会移动节点位置,整个 LRU 行为完全失效——看着像 LRU,实际是 FIFO。必须用三参数构造器:
-
new LinkedHashMap
(initialCapacity, 0.75f, true) —— 第三个参数 true 才激活访问顺序 - loadFactor 建议保持 0.75f:调高易触发哈希表扩容,打乱链表稳定性;调低则浪费空间
- accessOrder 是 final 字段,构造后不可修改,不能“先 new 再设”
正确重写 removeEldestEntry 控制容量
这个方法只在每次 put() 或 putAll() 后被调用一次,用于决定是否删除链表头部(即最久未用)的条目。它不响应 get(),也不保证线程安全,逻辑必须轻量、确定。
- 标准写法:return size() > capacity;(capacity 是你期望的最大条目数)
- 避免
size() >= capacity:会导致刚达上限就删,可能误删刚插入项 - 避免
size() > capacity + 1:会导致超容不删,缓存持续膨胀 - 方法体内禁止 I/O、日志、锁、复杂计算——它同步执行在 put 调用栈中
高频变量场景要防三类典型问题
高频变量生命周期短、访问密集,也更容易暴露底层细节漏洞。
-
value 可变性风险:若 value 是 ArrayList 或自定义对象,get() 返回的是原始引用,外部修改会污染缓存。建议返回
Collections.unmodifiableList或按需深拷贝 -
null value 歧义:put(key, null) 合法且参与排序,但 get() 返回 null 无法区分「未命中」和「值为 null」。如业务允许 null,统一用
Optional<v></v>包装 -
key 哈希失效:若用自定义对象作 key,必须正确重写
equals()和hashCode(),且字段不可变;否则哈希错位、重复插入、淘汰错位
并发安全不能靠“包装”来凑
LinkedHashMap 本身非线程安全。多个线程同时 get/put 可能导致 ConcurrentModificationException 或链表断裂。
- 低并发可加
ReentrantLock包裹关键操作,比Collections.synchronizedMap()更可控 - 忌用
synchronizedMap包装 LinkedHashMap:它只同步单个方法,无法保障 get+put 组合的原子性 - 高并发场景应直接选用 Caffeine、Guava Cache 等专业缓存库











