基于链表实现并发LRU缓存的优化与驱逐策略问询
针对ConcurrentCache+LRU的优化方案
一、解决LRU链表修改导致全局阻塞的更优实现
不要用全局同步块锁住整个链表,试试下面几种思路:
- 细粒度锁替代全局锁:不给整个链表加锁,而是给每个缓存节点绑定独立的锁(比如用节点对象本身作为锁,或ReentrantLock)。移动节点到头部时,只锁住当前节点、链表头节点这几个涉及的对象,不同账号的请求操作不同节点时,完全不会互相阻塞。
- CAS原子操作实现无锁链表:每个缓存节点用
AtomicReference维护前驱(prev)和后继(next)指针。移动节点到头部时,通过CAS原子更新指针:先把原节点的前后节点连起来,再把原节点设为新的头节点,整个过程不需要加锁,失败了就重试一次,不会阻塞其他线程。 - 读写路径分离:读操作(get)先直接从ConcurrentHashMap取节点,只有需要更新LRU顺序时才做CAS或细粒度锁操作。比如get到节点后,尝试用CAS把它移到头部,如果CAS失败(说明其他线程已经处理过),直接跳过也不影响正确性——毕竟LRU只是近似的淘汰策略,偶尔的顺序误差不影响整体效果。
二、避免缓存无界增长的替代方案
同步块里驱逐时size远超限制,本质是因为驱逐不及时或者size统计不准,试试这些方法:
- 预触发异步驱逐:别等缓存size达到上限才动手,设置一个预警阈值(比如上限的80%),当缓存计数达到这个值时,启动后台定时线程(用
ScheduledExecutorService)异步清理LRU末尾的节点。后台线程清理不影响前台请求,也能避免缓存突然暴涨。 - 写操作前置小量驱逐:每次执行put操作前,先检查当前缓存计数(用
AtomicInteger维护,别用ConcurrentHashMap的size(),它是近似值),如果超过上限,先驱逐一定数量的老节点(比如每次驱逐5-10个),再执行put。这里的驱逐用细粒度锁或者无锁方式,不会全局阻塞。 - 原子计数替代size():用
AtomicInteger来维护缓存的有效节点数,put成功就incrementAndGet(),驱逐成功就decrementAndGet()。ConcurrentHashMap的size()在高并发下是不准确的,原子计数能精准控制缓存规模,避免因为统计误差导致缓存膨胀。 - 分段缓存+分段驱逐:把缓存按账号哈希分成多个小缓存段,每个段维护自己的LRU链表和size上限。当某个段的size超过自身上限时,只驱逐该段的节点,不会影响其他段的请求,既分散了压力,也能避免全局无界增长。
内容的提问来源于stack exchange,提问作者super.t
相关产品推荐
相关产品推荐

