必须显式设置accessOrder=true并用三参数构造器,removeEldestEntry仅在put后触发且逻辑须为size()>capacity;高频变量需防value可变、null歧义及key哈希失效;低并发可用ReentrantLock,忌synchronizedMap。
要用 linkedhashmap 的访问顺序特性构建轻量级、适合高频变量的 lru 缓存,关键不在“写得多”,而在“设得准、控得稳、用得清”。它不是万能缓存框架,但对配置简单、读多写少、单线程或低并发场景下的热点变量(如用户会话标识、接口限流令牌、模板渲染参数等),非常高效且零依赖。
必须显式开启 accessOrder = true
这是整个 LRU 行为的开关,不是默认值。不设它,无论你怎么 get 或 put,链表顺序始终按插入顺序固化,removeEldestEntry 再怎么重写也淘汰不出“最久未用”的项——实际变成 FIFO,极易误判。
- 构造时必须用三参数版本:new LinkedHashMap
(initialCapacity, 0.75f, true) - loadFactor 建议保持 0.75f:调高易触发扩容(影响链表稳定性),调低浪费空间
- 不要试图“先建再改”:accessOrder 是 final 字段,构造后不可变
正确实现 removeEldestEntry 控制容量
这个方法只在每次 put() 或 putAll() 后被调用一次,用于决定是否删掉链表头节点(即最久未用项)。它不响应 get(),也不保证原子性,逻辑必须简洁精准。
- 标准写法:return size() > capacity;(capacity 是你期望的最大条目数)
- 避免写成
size() >= capacity或size() > capacity + 1:前者会导致刚达上限就删,后者导致超容不删 - 该方法内禁止 I/O、锁、日志、复杂计算——它在 put 调用栈中同步执行,拖慢所有写入
- 注意:第一次 put 也会触发,此时 size() == 1,确保 capacity ≥ 1,否则可能误删
警惕高频变量场景下的典型陷阱
高频变量往往生命周期短、访问密集、key/value 类型简单,但也更易暴露底层细节问题。
- value 可变性风险:若 value 是 ArrayList 或自定义对象,get() 返回的是原始引用,外部修改会污染缓存内容。建议返回不可变包装(如 Collections.unmodifiableList)或深拷贝(视性能要求权衡)
-
null value 边界:put(key, null) 合法且参与 LRU 排序;但 get() 返回 null 无法区分「未命中」和「命中但值为 null」。高频变量若允许 null,建议统一用 Optional
包装 value - key 的可靠性:高频 key(如 String、Long)通常安全;但若用自定义对象作 key,必须正确重写 equals/hashCode,且字段不可变,否则哈希错位、重复插入、淘汰失效全可能发生
轻量 ≠ 放弃线程安全底线
LinkedHashMap 本身非线程安全。高频变量常被多线程并发读取(如 Web 请求中的 request-scoped 变量),少量写入(如首次加载)。直接加 synchronized 方法会严重拖慢吞吐。
- 低并发:用 ReentrantLock 手动包裹 get/put 全流程,比 synchronized 更灵活
- 读远多于写:可考虑 CopyOnWriteMap 变体(需自行封装),或改用 ConcurrentHashMap + LinkedBlockingQueue 手动维护访问序(牺牲部分简洁性换安全性)
- 别用 Collections.synchronizedMap():它只同步单个方法,get+put 组合操作仍存在竞态,且 removeEldestEntry 不在同步范围内










