发布于2026-07-11 阅读(0)
扫一扫,手机访问
这部分内容,其实谈到底层原理并不复杂,但能把它用好、用对,才是日常开发中拉开差距的地方。就拿 accessOrder=true 这个参数来说,很多开发者知道它能实现LRU,但真要写一个健壮的缓存,里头门道不少。我们一个一个拆开看。

LinkedHashMap 的 accessOrder=true 能支持 LRU原因很简单。你构造 LinkedHashMap时,把第三个参数设为 true,它内部那条维护顺序的双向链表就不再看“谁先来”,而是看“谁最后被用”。每次你调用 get() 或者 put()(如果是更新已有key),它都会默默把那个节点挪到链表尾部。
这样一来,链表头部就永远蹲着那个“最久没被翻过牌子”的条目。淘汰时,你只需要把头节点干掉就行,效率很高。
需要留意的是,单纯新增一个key时,它只是追加到尾部,不会打乱其他节点的相对顺序。这其中的区别,你写代码的时候得想清楚。
removeEldestEntry() 控制缓存容量容量上限怎么控制?靠一个钩子方法——removeEldestEntry()。它每次 put() 结束后被自动调用,如果返回 true,框架就顺手把链表头(也就是最老的节点)删掉。这是实现容量限制的核心,不是你手动去轮询或者定时清理的。
关键点在于:
LinkedHashMap 然后重写这个方法,直接new出来的实例没法改。size() > capacity,别在里面写日志、搞IO这些耗时操作,会拖累性能。put() 内部是同步执行的,你不用额外加锁,但整个缓存仍然需要考虑并发安全。class LRUCacheextends LinkedHashMap { private final int capacity; LRUCache(int capacity) { // accessOrder = true super(capacity, 0.75f, true); this.capacity = capacity; } @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > capacity; } }
LinkedHashMap 子类会出什么问题很多人在这块栽过跟头。LinkedHashMap 本身可不是线程安全的。多线程环境下同时搞 get() 和 put(),链表结构会乱套,甚至死循环(JDK 7/8 时代有经典bug),或者直接抛 ConcurrentModificationException。
为了防杠,有人会直接加个 synchronized,但这招在“读多写少”的场景下直接让你的吞吐量惨不忍睹。用 Collections.synchronizedMap() 包一下呢?有个坑——removeEldestEntry() 并不在同步块里,可能会失效。
更稳妥的做法是:自己拿 ReentrantLock 手动锁住 get() 和 put() 的临界区。当然,如果你愿意放弃 LinkedHashMap 方案,直接用 ConcurrentHashMap 加上自定义的队列,也是条路。
get() 返回 null 时的边界行为缓存里没有key的时候,get() 返回 null,这不会触发链表调整,符合预期。但容易被忽略的是:put(key, null) 是合法的,它会正常参与LRU排序。如果业务允许value为 null,那么 get() 返回 null 时,你就分不清是“没命中”还是“命中了但值为null”。
建议是:
null value。可以在 put() 前做校验,发现 null 就直接抛 NullPointerException。Optional 包装返回值,或者额外维护一个 Set 来记录哪些key已经存在。computeIfAbsent() 这类衍生方法在 accessOrder 模式下同样会触发重排序,但你得小心lambda内部的递归调用,别把自己玩死了。说到底,accessOrder 模式只是一个高效的链表维护策略,它不帮你解决原子性、可见性、null 语义这些上层问题。它只优雅地做到了:帮你轻松判定“哪个是最近最久没被碰过的”。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8