本地緩存在Java中的實(shí)現(xiàn)過程
本地緩存是Java應(yīng)用中常用的性能優(yōu)化手段。如下圖所示:

在分布式系統(tǒng)中,同一個(gè)應(yīng)用部署有多個(gè),這些應(yīng)用的本地緩存僅限于本地應(yīng)用內(nèi)部,是互不相通的,在負(fù)載均衡中,分配到處理的各個(gè)應(yīng)用讀取本地緩存的結(jié)果可能會(huì)存在不一致。
注意:本地緩存是jvm層面的緩存,一旦該應(yīng)用重啟或停止了,緩存也消失了。
1、介紹
引入緩存,主要用于實(shí)現(xiàn)系統(tǒng)的高性能,高并發(fā)。如下圖所示:

將數(shù)據(jù)庫查詢出來的數(shù)據(jù)放入緩存服務(wù)中,因?yàn)榫彺媸谴鎯?chǔ)在內(nèi)存中的,內(nèi)存的讀寫性能遠(yuǎn)超磁盤的讀寫性能,所以訪問的速度非??臁?/strong>
注意:
但是電腦重啟后,內(nèi)存中的數(shù)據(jù)會(huì)全部清除,而磁盤中的數(shù)據(jù)雖然讀寫性能很差,但是數(shù)據(jù)不會(huì)丟失。
2、實(shí)現(xiàn)方式
2.1、HashMap
最簡單的方式是使用ConcurrentHashMap實(shí)現(xiàn)線程安全的緩存。
代碼示例如下:
import java.util.concurrent.ConcurrentHashMap;
public class SimpleCache<K, V> {
private final ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
public void put(K key, V value) {
cache.put(key, value);
}
public V get(K key) {
return cache.get(key);
}
public void remove(K key) {
cache.remove(key);
}
public void clear() {
cache.clear();
}
}適用場景:
- 簡單的內(nèi)存緩存需求
- 緩存數(shù)據(jù)量很?。◣装贄l以內(nèi))
- 不需要過期策略或淘汰機(jī)制
- 快速原型開發(fā)
優(yōu)點(diǎn):
- 零依賴
- 實(shí)現(xiàn)簡單直接
- 性能極高
缺點(diǎn):
- 缺乏過期、淘汰等高級(jí)功能
- 需要手動(dòng)實(shí)現(xiàn)線程安全(使用ConcurrentHashMap除外)
如下所示:
// 簡單的配置項(xiàng)緩存
private static final Map<String, String> CONFIG_CACHE = new ConcurrentHashMap<>();
public String getConfig(String key) {
return CONFIG_CACHE.computeIfAbsent(key, k -> loadConfigFromDB(k));
}2.2、LinkedHashMap
利用LinkedHashMap的訪問順序特性實(shí)現(xiàn)LRU(最近最少使用)緩存。
如下圖所示:
import java.util.LinkedHashMap;
import java.util.Map;
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
public LRUCache(int maxSize) {
super(maxSize, 0.75f, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}適用場景:
- 需要簡單的LRU淘汰策略
- 緩存數(shù)量固定且不大
- 不想引入第三方庫
優(yōu)點(diǎn):
- JDK內(nèi)置,無額外依賴
- 實(shí)現(xiàn)LRU策略簡單
缺點(diǎn):
- 功能有限
- 并發(fā)性能一般
如下所示:
// 最近訪問的用戶基本信息緩存
private static final int MAX_ENTRIES = 1000;
private static final Map<Long, UserInfo> USER_CACHE =
Collections.synchronizedMap(new LinkedHashMap<Long, UserInfo>(MAX_ENTRIES, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_ENTRIES;
}
});2.3、Guava Cache
Guava Cache是JVM層面的緩存,服務(wù)停掉或重啟便消失了,在分布式環(huán)境中也有其局限性。
因此,比較好的緩存方案是Guava Cache+Redis雙管齊下。先查詢Guava Cache,命中即返回,未命中再查redis。
引入依賴:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version> <!-- 使用最新版本 -->
</dependency>代碼如下所示:
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
public class GuavaCacheExample {
public static void main(String[] args) {
//建造者模式
Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100) // 最大緩存數(shù)量
.expireAfterWrite(10, TimeUnit.MINUTES) // 寫入后10分鐘過期
.build();
// 放入緩存
cache.put("key1", "value1");
// 獲取緩存
String value = cache.getIfPresent("key1");
System.out.println(value);
// 移除緩存
cache.invalidate("key1");
}
}適用場景:
- 需要豐富的緩存特性(過期、淘汰、刷新等)
- 中等規(guī)模緩存(幾千到幾十萬條目)
- 需要良好的并發(fā)性能
- 項(xiàng)目已經(jīng)使用Guava庫
優(yōu)點(diǎn):
- 功能全面(權(quán)重、刷新、統(tǒng)計(jì)等)
- 良好的API設(shè)計(jì)
- 中等規(guī)模的優(yōu)秀性能
缺點(diǎn):
- 不如Caffeine性能高
- 大型緩存時(shí)內(nèi)存效率一般
示例如下:
// 商品詳情緩存,30分鐘自動(dòng)過期,最大10000條
LoadingCache<Long, Product> productCache = CacheBuilder.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(30, TimeUnit.MINUTES)
.recordStats()
.build(new CacheLoader<Long, Product>() {
@Override
public Product load(Long id) {
return productDao.findById(id);
}
});
// 使用
Product product = productCache.get(123L);2.4、Caffeine Cache
Caffeine是Guava Cache的現(xiàn)代替代品,性能更好。
引入依賴:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version> <!-- 使用最新版本 -->
</dependency>代碼示例如下:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class CaffeineCacheExample {
public static void main(String[] args) {
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
cache.put("key1", "value1");
String value = cache.getIfPresent("key1");
System.out.println(value);
}
}適用場景:
- 高性能要求的應(yīng)用
- 大規(guī)模緩存(幾十萬以上條目)
- 需要最優(yōu)的讀寫性能
- 現(xiàn)代Java項(xiàng)目(JDK8+)
優(yōu)點(diǎn):
- 目前性能最好的Java緩存庫
- 內(nèi)存效率高
- 豐富的特性(異步加載、權(quán)重等)
- 優(yōu)秀的并發(fā)性能
缺點(diǎn):
- 較新的庫,老項(xiàng)目可能不適用
- API與Guava不完全兼容
示例如下:
// 高性能的秒殺商品庫存緩存
Cache<Long, AtomicInteger> stockCache = Caffeine.newBuilder()
.maximumSize(100_000)
.expireAfterWrite(10, TimeUnit.SECONDS) // 庫存信息短期有效
.refreshAfterWrite(1, TimeUnit.SECONDS) // 1秒后訪問自動(dòng)刷新
.build(id -> new AtomicInteger(queryStockFromDB(id)));
// 使用
int remaining = stockCache.get(productId).decrementAndGet();2.5、Ehcache
Ehcache是一個(gè)成熟的Java緩存框架:功能更強(qiáng)大,支持磁盤持久化、分布式緩存等。
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
</dependency>
import org.ehcache.Cache;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.config.units.MemoryUnit;
import org.ehcache.core.config.DefaultConfiguration;
import org.ehcache.core.spi.service.LocalPersistenceService;
import org.ehcache.impl.config.persistence.DefaultPersistenceConfiguration;
import org.ehcache.impl.persistence.DefaultLocalPersistenceService;
public class EhcacheExample {
public static void main(String[] args) {
// 配置持久化到磁盤
LocalPersistenceService persistenceService = new DefaultLocalPersistenceService(
new DefaultPersistenceConfiguration(new File("cache-data")));
// 創(chuàng)建緩存管理器
DefaultConfiguration config = new DefaultConfiguration(
persistenceService, ResourcePoolsBuilder.heap(100).build());
Cache<String, String> cache = CacheConfigurationBuilder.newCacheConfigurationBuilder(
String.class, String.class,
ResourcePoolsBuilder.newResourcePoolsBuilder()
.heap(100, MemoryUnit.MB) // 堆內(nèi)內(nèi)存
.disk(1, MemoryUnit.GB) // 磁盤持久化
).buildConfig(String.class);
// 寫入數(shù)據(jù)
cache.put("key1", "value1");
// 讀取數(shù)據(jù)
String value = cache.get("key1");
System.out.println("Value: " + value); // 輸出 Value: value1
// 關(guān)閉資源
persistenceService.close();
}
}
適用場景:
- 企業(yè)級(jí)應(yīng)用
- 需要持久化到磁盤
- 需要分布式緩存支持
- 復(fù)雜的緩存拓?fù)湫枨?/li>
優(yōu)點(diǎn):
- 功能最全面(堆外、磁盤、集群等)
- 成熟的監(jiān)控和管理
- 良好的Spring集成
缺點(diǎn):
- 性能不如Caffeine
- 配置較復(fù)雜
- 內(nèi)存效率一般
示例如下:
<!-- ehcache.xml -->
<cache name="financialDataCache"
maxEntriesLocalHeap="10000"
timeToLiveSeconds="3600"
memoryStoreEvictionPolicy="LFU">
<persistence strategy="localTempSwap"/>
</cache>// 金融數(shù)據(jù)緩存,需要持久化
@Cacheable(value = "financialDataCache",
key = "#symbol + '_' + #date.format(yyyyMMdd)")
public FinancialData getFinancialData(String symbol, LocalDate date) {
// 從外部API獲取數(shù)據(jù)
}2.6、使用Spring Cache注解
Spring框架提供了緩存抽象。關(guān)于cache的常用注解如下:

1、引入依賴
<dependencies>
<!-- Spring Boot Starter Cache -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- 使用Caffeine作為緩存實(shí)現(xiàn) -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
</dependencies>2、使用緩存配置類
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CaffeineCacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(500)
.expireAfterWrite(10, TimeUnit.MINUTES)
.recordStats());
return cacheManager;
}
@Bean
@Primary
public CacheManager productCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("products");
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS));
return cacheManager;
}
@Bean
public CacheManager userCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("users");
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(500)
.expireAfterAccess(30, TimeUnit.MINUTES));
return cacheManager;
}
}注意:在設(shè)置緩存配置類的時(shí)候,可以配置多個(gè)。
然后在服務(wù)類中指定使用哪個(gè)緩存管理器:
@Service
public class UserService {
@Cacheable(value = "users", cacheManager = "userCacheManager")
public User getUserById(Long id) {
// ...
}
}3、服務(wù)類使用緩存
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class ProductService {
// 根據(jù)ID獲取產(chǎn)品,如果緩存中有則直接返回
@Cacheable(value = "products", key = "#id")
public Product getProductById(Long id) {
// 模擬數(shù)據(jù)庫查詢
System.out.println("查詢數(shù)據(jù)庫獲取產(chǎn)品: " + id);
return findProductInDB(id);
}
// 更新產(chǎn)品信息,并更新緩存
@CachePut(value = "products", key = "#product.id")
public Product updateProduct(Product product) {
// 模擬數(shù)據(jù)庫更新
System.out.println("更新數(shù)據(jù)庫中的產(chǎn)品: " + product.getId());
return updateProductInDB(product);
}
// 刪除產(chǎn)品,并清除緩存
@CacheEvict(value = "products", key = "#id")
public void deleteProduct(Long id) {
// 模擬數(shù)據(jù)庫刪除
System.out.println("從數(shù)據(jù)庫刪除產(chǎn)品: " + id);
}
// 清除所有產(chǎn)品緩存
@CacheEvict(value = "products", allEntries = true)
public void clearAllCache() {
System.out.println("清除所有產(chǎn)品緩存");
}
// 模擬數(shù)據(jù)庫查詢方法
private Product findProductInDB(Long id) {
// 實(shí)際項(xiàng)目中這里應(yīng)該是數(shù)據(jù)庫操作
return new Product(id, "產(chǎn)品" + id, 100.0);
}
// 模擬數(shù)據(jù)庫更新方法
private Product updateProductInDB(Product product) {
// 實(shí)際項(xiàng)目中這里應(yīng)該是數(shù)據(jù)庫操作
return product;
}
}4、實(shí)體類
public class Product {
private Long id;
private String name;
private double price;
// 構(gòu)造方法、getter和setter省略
// 實(shí)際項(xiàng)目中應(yīng)該包含這些方法
}5、控制器示例:
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.getProductById(id);
}
@PutMapping
public Product updateProduct(@RequestBody Product product) {
return productService.updateProduct(product);
}
@DeleteMapping("/{id}")
public void deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
}
@PostMapping("/clear-cache")
public void clearCache() {
productService.clearAllCache();
}
}適用場景:
- Spring/Spring Boot項(xiàng)目
- 需要聲明式緩存
- 可能切換緩存實(shí)現(xiàn)
- 需要與Spring生態(tài)深度集成
優(yōu)點(diǎn):
- 統(tǒng)一的緩存抽象
- 注解驅(qū)動(dòng),使用簡單
- 輕松切換實(shí)現(xiàn)(Caffeine/Ehcache/Redis等)
缺點(diǎn):
- 性能取決于底層實(shí)現(xiàn)
- 高級(jí)功能需要了解底層實(shí)現(xiàn)
如下所示:
// 多級(jí)緩存配置:本地緩存+Redis
@Configuration
@EnableCaching
public class CacheConfig {
// 本地一級(jí)緩存
@Bean
@Primary
public CacheManager localCacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager();
manager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(30, TimeUnit.MINUTES));
return manager;
}
// Redis二級(jí)緩存
@Bean
public CacheManager redisCacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.builder(factory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(2))
.disableCachingNullValues())
.build();
}
}
// 服務(wù)層使用
@Service
public class ProductService {
@Cacheable(cacheNames = "products",
cacheManager = "localCacheManager") // 先用本地緩存
@Cacheable(cacheNames = "products",
cacheManager = "redisCacheManager",
unless = "#result == null") // 再用Redis緩存
public Product getProduct(Long id) {
return productRepository.findById(id);
}
}3、性能對比

1.合理設(shè)置緩存大小:
根據(jù)可用內(nèi)存設(shè)置上限。使用weigher對大型對象特殊處理。
2.選擇合適的過期策略:
// 根據(jù)業(yè)務(wù)場景選擇 .expireAfterWrite(10, TimeUnit.MINUTES) // 寫入后固定時(shí)間過期 .expireAfterAccess(30, TimeUnit.MINUTES) // 訪問后延長有效期 .refreshAfterWrite(1, TimeUnit.MINUTES) // 寫入后定時(shí)刷新
3.監(jiān)控緩存命中率:
CacheStats stats = cache.stats(); double hitRate = stats.hitRate(); // 命中率 long evictionCount = stats.evictionCount(); // 淘汰數(shù)量
4.避免緩存污染:
// 不緩存null或空值
.build(key -> {
Value value = queryFromDB(key);
return value == null ? Optional.empty() : value;
});
@Cacheable(unless = "#result == null || #result.isEmpty()")5.考慮使用軟引用(內(nèi)存敏感場景):
.softValues() // 內(nèi)存不足時(shí)自動(dòng)回收
根據(jù)您的具體業(yè)務(wù)需求、數(shù)據(jù)規(guī)模和性能要求,選擇最適合的緩存方案,并持續(xù)監(jiān)控和優(yōu)化緩存效果。
4、使用建議
簡單小規(guī)模緩存:ConcurrentHashMap或LinkedHashMap
- 適用于配置項(xiàng)、簡單查詢結(jié)果緩存
- 無外部依賴,實(shí)現(xiàn)簡單
中等規(guī)模通用緩存:Guava Cache或Caffeine
- 適用于大多數(shù)業(yè)務(wù)數(shù)據(jù)緩存
- Guava適合已有Guava依賴的項(xiàng)目
- Caffeine性能更好,推薦新項(xiàng)目使用
高性能大規(guī)模緩存:Caffeine
- 適用于高并發(fā)、高性能要求的場景
- 如秒殺系統(tǒng)、高頻交易系統(tǒng)
企業(yè)級(jí)復(fù)雜需求:Ehcache
- 需要持久化、集群等高級(jí)功能
- 已有Ehcache使用經(jīng)驗(yàn)的項(xiàng)目
Spring項(xiàng)目:Spring Cache + Caffeine
- 利用Spring抽象層,方便后續(xù)擴(kuò)展
- 推薦Caffeine作為底層實(shí)現(xiàn)
多級(jí)緩存架構(gòu):Caffeine + Redis
- 本地緩存作為一級(jí)緩存
- Redis作為二級(jí)分布式緩存
- 通過Spring Cache抽象統(tǒng)一管理
總結(jié)
內(nèi)存管理:設(shè)置合理的 maximumSize 或 expireAfterWrite,避免內(nèi)存溢出(OOM)。
并發(fā)安全:Guava/Caffeine/Ehcache 均為線程安全,直接使用即可。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot使用Validation實(shí)現(xiàn)接口校驗(yàn)的超全使用指南
這篇文章主要為大家詳細(xì)介紹了SpringBoot使用Validation實(shí)現(xiàn)接口校驗(yàn)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2026-03-03
手把手教你如何用JAVA連接MYSQL(mysql-connector-j-8.0.32.jar)
這篇文章主要介紹了關(guān)于如何用JAVA連接MYSQL(mysql-connector-j-8.0.32.jar)的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用MySQL具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01
Java使用Redis實(shí)現(xiàn)消息訂閱/發(fā)布的幾種方式
Redis 提供了 Pub/Sub (發(fā)布/訂閱) 模式,允許客戶端訂閱頻道并接收發(fā)布到這些頻道的消息,以下是 Java 中使用 Redis 實(shí)現(xiàn)消息訂閱的幾種方式,需要的朋友可以參考下2025-08-08
基于springboot服務(wù)間Feign調(diào)用超時(shí)的解決方案
這篇文章主要介紹了基于springboot服務(wù)間Feign調(diào)用超時(shí)的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Spring Boot與Kotlin 整合全文搜索引擎Elasticsearch的示例代碼
本篇文章主要介紹了Spring Boot與Kotlin 整合全文搜索引擎Elasticsearch的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
java省市級(jí)聯(lián)下拉菜單實(shí)例代碼
這篇文章主要為大家詳細(xì)介紹了java省市級(jí)聯(lián)實(shí)例代碼,具有一定的參考價(jià)值,感興趣的朋友可以參考一下2016-05-05
MyBatis-Plus多數(shù)據(jù)源配置與讀寫分離全過程
文章介紹了如何在SpringBoot中使用MyBatis-Plus實(shí)現(xiàn)多數(shù)據(jù)庫操作,包括純粹多庫和讀寫分離的配置方法,重點(diǎn)演示了多數(shù)據(jù)源的設(shè)置與使用2025-09-09
Maven導(dǎo)入本地jar包的實(shí)現(xiàn)步驟
本文主要介紹了Maven導(dǎo)入本地jar包的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07

