linkedhashmap 的顺序行为由构造函数第三个布尔参数决定:true 为访问顺序(lru 必需),false(默认)为插入顺序;removeeldestentry 在每次 put 后触发,返回 true 才删除最老项;它非线程安全,高并发需用 concurrenthashmap 或 caffeine 等替代方案。

为什么 LinkedHashMap 的构造参数决定顺序行为
关键在第二个布尔参数:传 true 启用访问顺序(access-order),false(默认)是插入顺序(insertion-order)。LRU 缓存必须用访问顺序,否则 removeEldestEntry 永远删的是最早插入、而非最久未用的项。
常见错误是只重写 removeEldestEntry 却忘了设 true,结果缓存行为完全不符合 LRU 预期——看起来像 FIFO。
- 插入顺序:
new LinkedHashMap(16, 0.75f, false)—— 迭代顺序 = put 顺序 - 访问顺序:
new LinkedHashMap(16, 0.75f, true)—— 每次get()或put()都把对应 entry 移到队尾 - 不设第三个参数等价于
false,LRU 场景下这是典型疏漏
removeEldestEntry 的触发时机和返回逻辑
这个方法在每次 put() 和 putAll() 后被调用,传入当前即将被加入的 eldest(即链表头节点)。它不控制“谁该被删”,而是回答“是否要删掉这个最老的”。
注意:它不会在 get() 后触发,所以容量控制只发生在写入路径;且返回 true 才真删,返回 false 则什么也不做。
立即学习“Java免费学习笔记(深入)”;
- 想实现固定大小 LRU:检查
size() > MAX_SIZE,满足则返回true - 不能依赖
eldest的 key/value 做复杂判断——它只是“当前最老的”,未必是“该淘汰的”,逻辑应尽量轻量 - 若重写了
put()或用了并发包装(如Collections.synchronizedMap),该方法仍有效,但需确保外部无额外同步干扰
线程安全不是默认选项,别指望 LinkedHashMap 自己扛
LinkedHashMap 本身非线程安全。多个线程同时 get() + put() 可能导致链表断裂、死循环(尤其在扩容时),JVM 甚至可能卡死。
常见误操作是加个 synchronized 块包住整个 get-put 流程,但这会严重拖慢吞吐——LRU 缓存本该高频读、低频写,锁粒度太大反而得不偿失。
- 推荐方案:用
java.util.concurrent.ConcurrentHashMap+ 手动维护访问顺序链表(复杂);或直接用caffeine/guava-cache - 退而求其次:用
Collections.synchronizedMap(new LinkedHashMap(...)),但所有访问(包括迭代)都得手动同步,否则仍不安全 - 绝对不要在 lambda 或 stream 中对非线程安全的
LinkedHashMap做并发操作
访问顺序模式下 get() 的开销比 HashMap 高一点
启用访问顺序后,每次 get() 不仅查哈希桶,还要把命中节点从链表中摘下、再插到尾部。链表操作是 O(1),但多了指针改写和内存访问,实测比 HashMap get() 慢 5%–10%。
这通常可接受,但如果你的场景是超高频只读(比如配置项缓存)、且极少触发淘汰,那插入顺序 + 手动更新访问时间戳可能更合适——不过这就脱离了 LinkedHashMap 的设计初衷。
- 性能敏感时,用 JMH 对比
get()吞吐量,别凭感觉 - 注意:
containsKey()和get()行为不同——前者不改变顺序,后者会 - 如果缓存 key 构造成本高(比如含正则或 JSON 解析),别在
removeEldestEntry里重复计算
true 却没同步,或者加了同步却没意识到 get() 本身已改链表结构——这两者叠在一起,问题会延迟暴露,复现困难。










