找回密码
 立即注册
首页 业界区 业界 工作中最常用的5种本地缓存

工作中最常用的5种本地缓存

祉遛吾 2026-1-14 10:25:03
前言

今天和大家聊聊一个几乎所有Java开发者都会用到,但很多人对其理解不够深入的技术——本地缓存
有些小伙伴在工作中可能遇到过这样的场景:系统性能测试时,发现某个接口响应时间越来越长,一查发现是数据库查询太频繁。
加了Redis分布式缓存后有所改善,但网络IO的开销依然存在,特别是对时效性要求高的数据。
这时候,本地缓存的价值就凸显出来了。
本地缓存是什么?
简单说就是将数据存储在应用进程的内存中,实现数据的快速访问。
它的访问速度通常是纳秒级,而分布式缓存(如Redis)是毫秒级,数据库查询更是毫秒到秒级。
01 为什么需要本地缓存?

在深入具体方案前,我们先搞清楚本地缓存的核心价值。
来看一个没有缓存的场景:
  1. // 没有缓存的用户查询服务@Servicepublic class CategoryServiceWithoutCache {        @Autowired    private CategoryMapper categoryMapper;        public Category getCategoryById(Long categoryId) {        // 每次调用都直接查询数据库        return categoryMapper.selectById(categoryId);    }        // 假设这个接口每秒被调用1000次    // 数据库就要承受1000QPS的压力}
复制代码
这种设计的问题很明显:

  • 数据库压力大:每次请求都要查询数据库
  • 响应时间长:数据库查询通常需要几毫秒到几十毫秒
  • 系统可扩展性差:数据库成为单点瓶颈
引入本地缓存后,情况会大大改善:
  1. // 使用本地缓存的用户查询服务@Servicepublic class CategoryServiceWithCache {        @Autowired    private CategoryMapper categoryMapper;        // 使用ConcurrentHashMap作为本地缓存    private final Map categoryCache = new ConcurrentHashMap();        public Category getCategoryById(Long categoryId) {        // 1. 先查缓存        Category category = categoryCache.get(categoryId);        if (category != null) {            return category;  // 缓存命中,直接返回        }                // 2. 缓存未命中,查询数据库        category = categoryMapper.selectById(categoryId);        if (category != null) {            // 3. 将数据放入缓存            categoryCache.put(categoryId, category);        }                return category;    }}
复制代码
这样改造后,对于同一个分类的多次查询,只有第一次会访问数据库,后续查询都直接返回缓存数据,性能提升几个数量级。
但是,简单的ConcurrentHashMap只是本地缓存的最基础形态。
在实际生产中,我们需要考虑更多问题:内存管理、过期策略、缓存淘汰、并发控制等。
这就是各种本地缓存框架存在的意义。
02 本地缓存核心要素

在介绍具体方案前,我们先了解一个优秀本地缓存应该具备的特性:

理解了这些核心要素,我们就能更好地评估各种缓存方案。
现在,让我们深入分析5种最常用的本地缓存方案。
03 方案一:ConcurrentHashMap

原理与特点

ConcurrentHashMap是JDK自发的线程安全的哈希表,它通过分段锁技术实现高并发访问。
作为缓存使用时,它是最简单、最轻量级的选择。

核心机制

  • 分段锁:将整个Map分成多个Segment,每个Segment独立加锁
  • 并发度:默认16个Segment,可支持16个线程并发写
  • Java 8优化:在Java 8中改为使用synchronized+CAS+红黑树,性能更好
实战代码示例
  1. import java.util.concurrent.*;/** * 基于ConcurrentHashMap的简易本地缓存 * 特点:简单、轻量、无过期策略 */public class ConcurrentHashMapCache {        // 核心缓存存储    private final ConcurrentHashMap cache;        // 可选的缓存加载器    private final CacheLoader loader;        public ConcurrentHashMapCache() {        this.cache = new ConcurrentHashMap();        this.loader = null;    }        public ConcurrentHashMapCache(CacheLoader loader) {        this.cache = new ConcurrentHashMap();        this.loader = loader;    }        /**     * 获取缓存值     * 如果不存在且配置了loader,则加载数据     */    public V get(K key) {        V value = cache.get(key);                if (value == null && loader != null) {            // 双重检查锁,防止并发加载            synchronized (this) {                value = cache.get(key);                if (value == null) {                    value = loader.load(key);                    if (value != null) {                        cache.put(key, value);                    }                }            }        }                return value;    }        /**     * 放入缓存     */    public void put(K key, V value) {        cache.put(key, value);    }        /**     * 移除缓存     */    public V remove(K key) {        return cache.remove(key);    }        /**     * 清空缓存     */    public void clear() {        cache.clear();    }        /**     * 获取缓存大小     */    public int size() {        return cache.size();    }        /**     * 判断是否包含key     */    public boolean containsKey(K key) {        return cache.containsKey(key);    }        /**     * 缓存加载器接口     */    @FunctionalInterface    public interface CacheLoader {        V load(K key);    }}/** * 使用示例:用户信息缓存 */@Servicepublic class UserServiceWithConcurrentHashMap {        @Autowired    private UserMapper userMapper;        // 使用自定义的ConcurrentHashMap缓存    private final ConcurrentHashMapCache userCache;        public UserServiceWithConcurrentHashMap() {        // 初始化缓存,配置缓存加载器        userCache = new ConcurrentHashMapCache(userId -> {            // 当缓存不存在时,调用此方法加载数据            System.out.println("缓存未命中,从数据库加载用户: " + userId);            return userMapper.selectById(userId);        });    }        /**     * 获取用户信息(带缓存)     */    public User getUserById(Long userId) {        return userCache.get(userId);    }        /**     * 更新用户信息(同时更新缓存)     */    public void updateUser(User user) {        // 1. 更新数据库        userMapper.updateById(user);                // 2. 更新缓存        userCache.put(user.getId(), user);    }        /**     * 删除用户(同时删除缓存)     */    public void deleteUser(Long userId) {        // 1. 删除数据库记录        userMapper.deleteById(userId);                // 2. 删除缓存        userCache.remove(userId);    }        /**     * 批量获取用户(优化N+1查询)     */    public Map getUsersByIds(List userIds) {        Map result = new HashMap();        List missingIds = new ArrayList();                // 1. 先从缓存获取        for (Long userId : userIds) {            User user = userCache.get(userId);            if (user != null) {                result.put(userId, user);            } else {                missingIds.add(userId);            }        }                // 2. 批量查询缺失的数据        if (!missingIds.isEmpty()) {            List dbUsers = userMapper.selectBatchIds(missingIds);            for (User user : dbUsers) {                result.put(user.getId(), user);                userCache.put(user.getId(), user);  // 放入缓存            }        }                return result;    }}
复制代码
优缺点分析

优点:

  • JDK原生支持:无需引入第三方依赖
  • 线程安全:分段锁/CAS保证并发安全
  • 性能优秀:读操作完全无锁,写操作锁粒度小
  • 简单轻量:代码简单,资源消耗小
缺点:

  • 功能有限:缺乏过期、淘汰等高级功能
  • 内存无法限制:可能造成内存溢出
  • 需要手动管理:缓存策略需要自己实现
适用场景:

  • 缓存数据量小且固定
  • 不需要自动过期功能
  • 作为更复杂缓存的底层实现
  • 临时性、简单的缓存需求
有些小伙伴在项目初期喜欢用ConcurrentHashMap做缓存,因为它简单直接。
但随着业务复杂化,往往会遇到内存溢出、缓存清理等问题,这时候就需要更专业的缓存方案了。
04 方案二:LRU缓存

原理与特点

LRU(Least Recently Used,最近最少使用)是一种经典的缓存淘汰算法。
当缓存空间不足时,优先淘汰最久未被访问的数据。
LinkedHashMap是JDK提供的实现了LRU特性的Map实现。
它通过维护一个双向链表来记录访问顺序。

核心机制

  • 访问顺序:accessOrder=true时,每次访问会将节点移到链表头部
  • 淘汰策略:链表尾部的节点是最久未访问的
  • 重写removeEldestEntry:控制何时删除最老节点
实战代码示例

[code]import java.util.*;/** * 基于LinkedHashMap的LRU缓存 * 特点:自动淘汰最久未使用的数据 */public class LRUCache extends LinkedHashMap {        private final int maxCapacity;        /**     * 创建LRU缓存     * @param maxCapacity 最大容量     */    public LRUCache(int maxCapacity) {        // 第三个参数accessOrder为true表示按访问顺序排序        super(16, 0.75f, true);        this.maxCapacity = maxCapacity;    }        /**     * 重写此方法决定何时删除最老的条目     * @param eldest 最老的条目(最近最少使用)     * @return true表示应该删除最老的条目     */    @Override    protected boolean removeEldestEntry(Map.Entry eldest) {        // 当大小超过最大容量时,删除最老的条目        return size() > maxCapacity;    }        /**     * 获取缓存值(会更新访问顺序)     */    @Override    public V get(Object key) {        synchronized (this) {            return super.get(key);        }    }        /**     * 放入缓存值     */    @Override    public V put(K key, V value) {        synchronized (this) {            return super.put(key, value);        }    }        /**     * 获取缓存统计信息     */    public CacheStats getStats() {        return new CacheStats(size(), maxCapacity);    }        /**     * 获取最近访问的N个键     */    public List getRecentlyAccessed(int n) {        List result = new ArrayList();        Iterator iterator = keySet().iterator();                for (int i = 0; i < n && iterator.hasNext(); i++) {            result.add(iterator.next());        }                return result;    }        /**     * 缓存统计信息     */    public static class CacheStats {        private final int currentSize;        private final int maxCapacity;        private final double usageRatio;                public CacheStats(int currentSize, int maxCapacity) {            this.currentSize = currentSize;            this.maxCapacity = maxCapacity;            this.usageRatio = maxCapacity > 0 ? (double) currentSize / maxCapacity : 0;        }                // getters...    }}/** * 线程安全的LRU缓存实现 */public class ConcurrentLRUCache {        private final LRUCache cache;        public ConcurrentLRUCache(int maxCapacity) {        this.cache = new LRUCache(maxCapacity);    }        /**     * 获取缓存值     */    public V get(K key) {        synchronized (cache) {            return cache.get(key);        }    }        /**     * 放入缓存值     */    public V put(K key, V value) {        synchronized (cache) {            return cache.put(key, value);        }    }        /**     * 批量放入缓存     */    public void putAll(Map

相关推荐

2026-2-1 00:25:07

举报

2026-2-3 06:57:18

举报

2026-2-3 07:03:55

举报

2026-2-5 06:44:57

举报

2026-2-9 09:34:51

举报

感谢发布原创作品,程序园因你更精彩
2026-2-12 08:09:07

举报

2026-2-25 03:15:44

举报

2026-2-27 01:01:18

举报

2026-3-7 07:57:03

举报

12下一页
您需要登录后才可以回帖 登录 | 立即注册