高并发场景下,如何用无锁实现高性能LRU缓存?

《百万人高并发场景下,我如何用无锁实现高性能LRU缓存?》

LRU算法核心原理

LRU(Least Recently Used)算法是缓存系统的核心淘汰策略,其核心逻辑可以用一张流程图描述:

(图:访问数据时触发链表重组,新增数据时触发淘汰检测)


一、分段锁设计思路

  • 分段缓存(Segment):
    将整个缓存按 key 的 hash 值划分为多个 Segment,每个 Segment 内部维护一个小型 LRU 缓存(HashMap + 双向链表)。

  • 独立锁:
    每个 Segment 都持有自己的 ReentrantLock,操作各自区域的数据时只锁定当前 Segment,而不同 Segment 的操作可以并发执行。

  • 整体映射:
    外层类根据 key 的 hash 值决定使用哪个 Segment,从而在降低锁竞争的同时仍能保证整体缓存的线程安全。


二、分段锁 LRU 缓存代码实现

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;/*** SegmentedLRUCache 是一个基于分段锁的线程安全 LRU 缓存实现。* 通过将缓存划分为多个 Segment,每个 Segment 内部维护自己的 HashMap 和双向链表,* 有效降低了全局锁的竞争,提高了并发性能。** @param <K> 键的类型* @param <V> 值的类型*/
public class SegmentedLRUCache<K, V> {/*** 每个 Segment 内部的缓存实现,包含一个 HashMap 和双向链表,并用 ReentrantLock 保护操作。*/private static class Segment<K, V> {private final int capacity;                    // 当前 Segment 的容量private final Map<K, Node<K, V>> map;          // 快速查找 key 对应节点private Node<K, V> head;                       // 双向链表头,最近使用的数据private Node<K, V> tail;                       // 双向链表尾,最久未使用的数据private final ReentrantLock lock = new ReentrantLock();public Segment(int capacity) {this.capacity = capacity;this.map = new HashMap<>();}public V get(K key) {lock.lock();try {Node<K, V> node = map.get(key);if (node == null) {return null;}// 访问后将节点移动到链表头部moveToHead(node);return node.value;} finally {lock.unlock();}}public void put(K key, V value) {lock.lock();try {Node<K, V> node = map.get(key);if (node != null) {// 更新值并移动到头部node.value = value;moveToHead(node);} else {// 如果超出容量,则移除链表尾部节点(最久未使用)if (map.size() >= capacity) {if (tail != null) {map.remove(tail.key);removeNode(tail);}}Node<K, V> newNode = new Node<>(key, value);addNodeAtHead(newNode);map.put(key, newNode);}} finally {lock.unlock();}}/*** 将节点移动到链表头部。*/private void moveToHead(Node<K, V> node) {if (node == head) {return;}removeNode(node);addNodeAtHead(node);}/*** 从链表中移除节点。*/private void removeNode(Node<K, V> node) {if (node.prev != null) {node.prev.next = node.next;} else {head = node.next;}if (node.next != null) {node.next.prev = node.prev;} else {tail = node.prev;}node.prev = null;node.next = null;}/*** 将节点添加到链表头部。*/private void addNodeAtHead(Node<K, V> node) {node.next = head;node.prev = null;if (head != null) {head.prev = node;}head = node;if (tail == null) {tail = node;}}/*** 调试使用:打印当前 Segment 内缓存的状态(从头到尾)。*/public void printSegment() {lock.lock();try {Node<K, V> current = head;while (current != null) {System.out.print(current.key + ":" + current.value + "  ");current = current.next;}System.out.println();} finally {lock.unlock();}}}/*** 双向链表节点,保存缓存中的键值对及前后指针。*/private static class Node<K, V> {K key;V value;Node<K, V> prev;Node<K, V> next;public Node(K key, V value) {this.key = key;this.value = value;}}private final int segmentCount;         // Segment 数量private final Segment<K, V>[] segments;   // 分段数组/*** 构造方法** @param capacity     整个缓存的总容量* @param segmentCount 分段数量,建议取 16 或 32*/@SuppressWarnings("unchecked")public SegmentedLRUCache(int capacity, int segmentCount) {if (capacity < segmentCount) {throw new IllegalArgumentException("容量必须大于或等于分段数量");}this.segmentCount = segmentCount;segments = new Segment[segmentCount];// 将总容量均摊到各个 Segment 上int capacityPerSegment = capacity / segmentCount;for (int i = 0; i < segmentCount; i++) {segments[i] = new Segment<>(capacityPerSegment);}}/*** 根据 key 的 hash 值定位到对应的 Segment。*/private Segment<K, V> segmentFor(K key) {int h = key.hashCode();// 使用 & 运算保证非负,再取模int index = (h & 0x7fffffff) % segmentCount;return segments[index];}/*** 获取指定 key 对应的值。*/public V get(K key) {if (key == null) throw new NullPointerException();return segmentFor(key).get(key);}/*** 插入或更新一个键值对。*/public void put(K key, V value) {if (key == null) throw new NullPointerException();segmentFor(key).put(key, value);}/*** 调试方法:打印所有 Segment 中缓存的状态。*/public void printCache() {for (int i = 0; i < segmentCount; i++) {System.out.print("Segment " + i + ": ");segments[i].printSegment();}}/*** 测试入口:演示 SegmentedLRUCache 在多线程环境下的表现。*/public static void main(String[] args) {// 总容量为 16,分成 4 个 Segmentfinal SegmentedLRUCache<Integer, String> cache = new SegmentedLRUCache<>(16, 4);// 写线程:不断插入数据Runnable writer = () -> {for (int i = 0; i < 50; i++) {cache.put(i, "Value" + i);System.out.println(Thread.currentThread().getName() + " put: " + i + " => Value" + i);try {Thread.sleep(20);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}};// 读线程:随机读取数据Runnable reader = () -> {for (int i = 0; i < 50; i++) {int key = (int) (Math.random() * 50);String value = cache.get(key);System.out.println(Thread.currentThread().getName() + " get: " + key + " => " + value);try {Thread.sleep(30);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}};Thread writerThread1 = new Thread(writer, "Writer-1");Thread writerThread2 = new Thread(writer, "Writer-2");Thread readerThread1 = new Thread(reader, "Reader-1");Thread readerThread2 = new Thread(reader, "Reader-2");writerThread1.start();writerThread2.start();readerThread1.start();readerThread2.start();try {writerThread1.join();writerThread2.join();readerThread1.join();readerThread2.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("Final cache state:");cache.printCache();}
}

三、代码详解

  1. 分段划分:

    • 构造方法中,我们将总容量均摊到各个 Segment,每个 Segment 内部维护一个 HashMap 和双向链表。
    • 方法 segmentFor(key) 根据 key 的 hash 值计算出所属的 Segment,从而确保不同 key 分布到不同的 Segment。
  2. 细粒度锁定:

    • 每个 Segment 内部用 ReentrantLock 对操作进行保护,保证在同一 Segment 内的并发操作安全。
    • 不同 Segment 之间互不干扰,极大减少了锁竞争。
  3. LRU 实现细节:

    • 每个 Segment 内的双向链表用于记录访问顺序。
    • 访问(get)或更新(put)时,都会将对应节点移到链表头部;当容量超限时,移除尾部节点(最久未使用的数据)。
  4. 多线程测试:

    • main 方法中,我们启动多个读写线程,模拟高并发访问场景,并最终打印每个 Segment 内的缓存状态以验证正确性。

四、总结

通过分段锁设计,我们将单一全局锁的竞争问题转化为多个独立锁的局部竞争,从而在高并发场景下显著提升 LRU 缓存的性能。

兼顾了线程安全和高效性,非常适用于对缓存响应时间要求较高的业务场景。源码经过测试,拿走即用,你可以根据实际情况调整分段数及容量,进一步优化性能。

最后


欢迎关注gzh:加瓦点灯,每天推送干货知识!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/17921.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HAL库框架学习总结

概述&#xff1a;HAL库为各种外设基本都配了三套 API&#xff0c;查询&#xff0c;中断和 DMA。 一、HAL库为外设初始化提供了一套框架&#xff0c;这里以串口为例进行说明&#xff0c;调用函数 HAL_UART_Init初始化串口&#xff0c;此函数就会调用 HAL_UART_MspInit&#xff0…

LAWS是典型的人机环境系统

致命性自主武器系统&#xff08;Lethal Autonomous Weapons Systems&#xff0c;LAWS&#xff09;是一种典型的人机环境系统&#xff0c;它通过高度集成的传感器、算法和武器平台&#xff0c;在复杂的战场环境中自主执行任务。LAWS能够自主感知环境、识别目标、做出决策并实施攻…

【16届蓝桥杯寒假刷题营】第1期DAY4

4.可达岛屿的个数 - 蓝桥云课 题目背景 在一个神奇的魔法世界中&#xff0c;有一座古老的迷幻之城。迷幻之城被分成 n 个鸟屿&#xff0c;编号从 1 到 n&#xff0c;共有 m 座桥。迷幻之城的居民们希望能够建立起紧密的联系&#xff0c;每个岛屿上的居民都想知道自己最多能到…

【物联网】电子电路基础知识

文章目录 一、基本元器件1. 电阻2. 电容3. 电感4. 二极管(1)符号(2)特性(3)实例分析5. 三极管(1)符号(2)开关特性(3)实例6. MOS管(产效应管)(1)符号(2)MOS管极性判定(3)MOS管作为开关(4)MOS管vs三极管7. 门电路(1)与门(2)或门(3)非门二、常用元器件…

数据结构 04

4. 栈 4.2. 链式栈 4.2.1. 特性 逻辑结构&#xff1a;线性结构 存储结构&#xff1a;链式存储结构 操作&#xff1a;创建&#xff0c;入栈&#xff0c;出栈&#xff0c;清空&#xff0c;获取 4.2.2. 代码实现 头文件 LinkStack.h #ifndef __LINKSTACK_H__ #define __LINKST…

【云安全】云原生-K8S(四)安全问题分析

Kubernetes&#xff08;K8S&#xff09;因其强大的容器编排能力成为了云计算和微服务架构的首选&#xff0c;但同时也带来了复杂的安全挑战。本文将概述K8S的主要安全问题&#xff0c;帮助安全工程师理解潜在威胁&#xff0c;并采取相应的防护措施。 K8S 攻击面概览 下面两张…

【Unity新手】Text不显示字的问题解决办法

很多同学在unity里导入了一个Text发现字没有显示出来为什么呢&#xff1f; 首先在网络上下载一个.ttf或者.otf字体文件&#xff0c;导入资源&#xff0c;比如说我下载了黑体.otf 然后导入unity&#xff0c;右键字体TextMesgPro-FontAsset 然后字体设置里添加上就可以了

基于Flask的影视剧热度数据可视化分析系统的设计与实现

【FLask】基于Flask的影视剧热度数据可视化分析系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 随着互联网技术的飞速发展&#xff0c;影视剧行业的数据量呈爆炸性增长&#x…

React 低代码项目:组件设计

React 低代码项目&#xff1a;组件设计 Date: February 6, 2025 React表单组件 **目标&#xff1a;**使用 Ant Design 表单组件&#xff0c;开发登录、注册、搜索功能 内容&#xff1a; 使用 React 表单组件、受控组件使用 Ant Design 表单组件使用 表单组件的校验和错误提…

vue-plugin-hiprint (vue2

页面效果 <template><div><div class="d-flex flex-column mt5"><div class="d-flex flex-row " style="margin-bottom: 10px;justify-content: center;"><!-- 纸张大小 A3、A4 等 --><div class="paper…

C++17 中的 std::reduce:详细教程

文章目录 1. 简介2. 函数签名3. 使用场景3.1 简单的累加操作3.2 自定义归并操作3.3 并行计算的性能优势 4. 注意事项4.1 归并操作的结合律和交换律4.2 默认值的使用 5. 总结 1. 简介 std::reduce 是 C17 标准库中引入的一个算法&#xff0c;用于对范围内的元素进行归并操作。它…

kafka介绍,kafka集群环境搭建,kafka命令测试,C++实现kafka客户端

目录 kafka介绍kafka集群环境搭建zookeeper安装与配置kafka安装与配置 kafka命令测试C实现kafka客户端librdkafka库编译新版本cmake编译cppkafka库编译C实现kafka生产者和消费者客户端 kafka介绍 定义与概述 Apache Kafka 是一个开源的分布式流处理平台&#xff0c;最初由 Lin…

华为云+硅基流动使用Chatbox接入DeepSeek-R1满血版671B

华为云硅基流动使用Chatbox接入DeepSeek-R1满血版671B 硅基流动 1.1 注册登录 1.2 实名认证 1.3 创建API密钥 1.4 客户端工具 OllamaChatboxCherry StudioAnythingLLM 资源包下载&#xff1a; AI聊天本地客户端 接入Chatbox客户端 点击设置 选择SiliconFloW API 粘贴1.3创…

阿里云百炼平台对接DeepSeek官方文档

目录 1、支持的模型 2、快速开始 2.1、OpenAI兼容 2.1.1、python示例代码 返回结果 2.1.2、Node.js示例代码 返回结果 2.1.3、HTTP示例代码 返回结果 2.2、DashScope 2.2.1、python示例代码 返回结果 2.2.2、java示例代码 返回结果 2.2.3、HTTP代码示例 返回结…

【深度强化学习】策略梯度算法:REINFORCE

策略梯度 强化学习算法进阶 Q-learning、DQN 及 DQN 改进算法都是基于价值&#xff08;value-based&#xff09;的方法&#xff0c;其中 Q-learning 是处理有限状态的算法&#xff0c;而 DQN 可以用来解决连续状态的问题。在强化学习中&#xff0c;除了基于值函数的方法&#…

DeepSeek接口联调(postman版)

第一步&#xff1a;获取API key 获取APIkeys链接https://platform.deepseek.com/api_keys 点击创建 API key 即可免费生成一个key值&#xff0c;别忘记保存。 第二步&#xff1a;找到deepseek官方接口文档 文档地址&#xff1a;https://api-docs.deepseek.com/zh-cn/ 第三步…

Sublime Text 3 中的 Pylinter 配置

在 Sublime Text 3 中配置 Pylinter&#xff08;如 pylint&#xff09;来进行 Python 代码静态分析&#xff0c;可以帮助你提升代码质量、检测潜在的错误、强制遵守编码标准等。为了在 Sublime Text 3 中配置 pylint&#xff0c;你需要确保 pylint 已安装&#xff0c;并设置好相…

LC-搜索二维矩阵II、相交链表、反转链表、回文链表、环形链表、环形链表ll

搜索二维矩阵II 方法&#xff1a;从右上角开始搜索 我们可以从矩阵的右上角开始进行搜索。如果当前元素 matrix[i][j] 等于 target&#xff0c;我们直接返回 true。如果 matrix[i][j] 大于 target&#xff0c;说明 target 只能出现在左边的列&#xff0c;所以我们将列指针向左…

支持列表拖拽嵌套,AI流式输出的多模态文档编辑器flowmix/docx: 全面升级

hi, 大家好, 我是徐小夕. 马上又到周五了, 最近也收到很多用户对 flowmix/docx 多模态文档编辑器的反馈&#xff0c;我们也做了一波新功能的升级&#xff0c;今天就和大家分享一下 flowmix/docx 多模态文档编辑器的最新更新. 演示地址: https://flowmix.turntip.cn/docx 以下是…

服务器中部署大模型DeepSeek-R1 | 本地部署DeepSeek-R1大模型 | deepseek-r1部署详细教程

0. 部署前的准备 首先我们需要足够算力的机器&#xff0c;这里我在vultr中租了有一张A16显卡一共16GB显存的服务器作为演示。部署的模型参数为14b的。如果需要部署满血版本671b的&#xff0c;需要更大的算力支持&#xff0c;这里由于是个人资金有限&#xff0c;就演示14b的部署…