要用 linkedhashmap 实现轻量级 lru 缓存,必须设 accessorder = true、精准实现 removeeldestentry(如 return size() > capacity)、规避可变 value、null 值歧义及 key 哈希失效,并在低并发场景使用,高并发应选 caffeine 等专业缓存。

要用 LinkedHashMap 实现轻量级 LRU 缓存,核心不是“写个类就行”,而是把 accessOrder = true 设对、removeEldestEntry 逻辑写准、并避开高频变量场景下的典型陷阱。它适合读多写少、单线程或低并发的热点数据缓存,比如用户会话 ID、模板参数、限流令牌等。
必须显式启用访问顺序模式
LinkedHashMap 默认是插入顺序(accessOrder = false),不开启访问顺序,get() 操作不会移动节点位置,整个 LRU 行为就失效了——看着像 LRU,实际是 FIFO。
- 构造时一定要用三参数版本:new LinkedHashMap
(initialCapacity, 0.75f, 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 或链表断裂。
-
Collections.synchronizedMap()不推荐:仅同步单个方法,removeEldestEntry 不在同步块内,行为不可控 - 低并发可用
ReentrantLock手动包裹 get/put 全流程,但吞吐会下降 - 高并发场景建议直接选用 Caffeine 或 Guava Cache,而非硬改 LinkedHashMap











