本文目录导读:

- 简单HashMap缓存(不推荐生产使用)
- 支持过期时间的缓存
- 使用LinkedHashMap实现LRU缓存
- 使用Guava Cache(推荐)
- 使用Caffeine Cache(高性能推荐)
- Redis分布式缓存
- Spring Cache抽象
- 选择建议
- 最佳实践
在Java中实现缓存有多种方式,从简单的内存缓存到分布式缓存,下面我将介绍几种常见的实现方案,从简单到复杂。
简单HashMap缓存(不推荐生产使用)
最基本的实现方式,适合学习理解缓存原理:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SimpleCache<K, V> {
private final Map<K, V> cache = new HashMap<>();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public V get(K key) {
lock.readLock().lock();
try {
return cache.get(key);
} finally {
lock.readLock().unlock();
}
}
public void put(K key, V value) {
lock.writeLock().lock();
try {
cache.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
public void remove(K key) {
lock.writeLock().lock();
try {
cache.remove(key);
} finally {
lock.writeLock().unlock();
}
}
public void clear() {
lock.writeLock().lock();
try {
cache.clear();
} finally {
lock.writeLock().unlock();
}
}
}
支持过期时间的缓存
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
public class ExpiringCache<K, V> {
private final ConcurrentHashMap<K, CacheEntry<V>> cache = new ConcurrentHashMap<>();
private static class CacheEntry<V> {
private final V value;
private final long expiryTime;
public CacheEntry(V value, long expiryTime) {
this.value = value;
this.expiryTime = expiryTime;
}
public boolean isExpired() {
return System.currentTimeMillis() > expiryTime;
}
}
public V get(K key) {
CacheEntry<V> entry = cache.get(key);
if (entry == null) {
return null;
}
if (entry.isExpired()) {
cache.remove(key);
return null;
}
return entry.value;
}
public void put(K key, V value, long duration, TimeUnit timeUnit) {
long expiryTime = System.currentTimeMillis() + timeUnit.toMillis(duration);
cache.put(key, new CacheEntry<>(value, expiryTime));
}
public void remove(K key) {
cache.remove(key);
}
public int size() {
// 清理过期项并返回有效数量
cache.entrySet().removeIf(entry -> entry.getValue().isExpired());
return cache.size();
}
}
使用LinkedHashMap实现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); // true = access-order
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
// 使用示例
public static void main(String[] args) {
LRUCache<String, String> cache = new LRUCache<>(3);
cache.put("A", "Value A");
cache.put("B", "Value B");
cache.put("C", "Value C");
cache.get("A"); // 访问A,使其成为最近使用的
cache.put("D", "Value D"); // B将被移除(最久未使用)
System.out.println(cache.containsKey("B")); // false
System.out.println(cache.get("A")); // Value A
}
}
使用Guava Cache(推荐)
Guava提供了功能完善的缓存实现,是Java缓存的首选方案之一:
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
public class GuavaCacheExample {
// 方式1:手动创建和管理
public Cache<String, User> createManualCache() {
return CacheBuilder.newBuilder()
.maximumSize(1000) // 最大条目数
.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后过期
.expireAfterAccess(5, TimeUnit.MINUTES) // 访问后过期
.recordStats() // 记录统计信息
.build();
}
// 方式2:自动加载缓存
public LoadingCache<String, User> createLoadingCache() {
return CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<String, User>() {
@Override
public User load(String key) throws Exception {
// 从数据库或其他数据源加载
return getUserFromDatabase(key);
}
}
);
}
// 使用示例
public void demo() {
LoadingCache<String, User> cache = createLoadingCache();
try {
// 自动加载,如果缓存中没有则调用CacheLoader.load()
User user = cache.get("user123");
System.out.println(user);
// 统计信息
System.out.println("命中率: " + cache.stats().hitRate());
} catch (Exception e) {
e.printStackTrace();
}
}
private User getUserFromDatabase(String userId) {
// 模拟数据库查询
return new User(userId, "John Doe");
}
static class User {
private String id;
private String name;
public User(String id, String name) {
this.id = id;
this.name = name;
}
}
}
使用Caffeine Cache(高性能推荐)
Caffeine是高并发场景下性能最优的Java缓存实现:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
public class CaffeineCacheExample {
public Cache<String, Data> createCache() {
return Caffeine.newBuilder()
.maximumSize(10_000) // 最大条目数
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后过期
.expireAfterAccess(3, TimeUnit.MINUTES) // 访问后过期
.refreshAfterWrite(1, TimeUnit.MINUTES) // 自动刷新
.recordStats() // 记录统计
.build();
}
public LoadingCache<String, Data> createLoadingCache() {
return Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build(key -> loadDataFromSource(key));
}
private Data loadDataFromSource(String key) {
// 从数据库或远程服务加载数据
return new Data(key, System.currentTimeMillis());
}
// 高级用法:异步加载
public AsyncLoadingCache<String, Data> createAsyncCache() {
return Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.buildAsync(key -> loadDataFromSource(key));
}
public void demo() {
LoadingCache<String, Data> cache = createLoadingCache();
// 获取缓存,不存在则自动加载
Data data = cache.get("key1");
System.out.println(data);
// 批量获取
Map<String, Data> allData = cache.getAll(Set.of("key1", "key2", "key3"));
allData.forEach((key, value) -> System.out.println(key + ": " + value));
// 手动失效
cache.invalidate("key1");
// 统计信息
System.out.println("请求次数: " + cache.stats().requestCount());
System.out.println("命中率: " + cache.stats().hitRate());
}
static class Data {
private String id;
private long timestamp;
public Data(String id, long timestamp) {
this.id = id;
this.timestamp = timestamp;
}
}
}
Redis分布式缓存
对于分布式系统,使用Redis作为缓存:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class RedisCacheExample {
private final JedisPool jedisPool;
public RedisCacheExample(String host, int port) {
this.jedisPool = new JedisPool(host, port);
}
public void set(String key, String value, int expirySeconds) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.setex(key, expirySeconds, value);
}
}
public String get(String key) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.get(key);
}
}
public boolean exists(String key) {
try (Jedis jedis = jedisPool.getResource()) {
return jedis.exists(key);
}
}
public void delete(String key) {
try (Jedis jedis = jedisPool.getResource()) {
jedis.del(key);
}
}
// 使用对象序列化存储复杂对象
public <T> void setObject(String key, T obj, int expirySeconds) {
try (Jedis jedis = jedisPool.getResource()) {
byte[] data = serialize(obj);
jedis.setex(key.getBytes(), expirySeconds, data);
}
}
public <T> T getObject(String key, Class<T> clazz) {
try (Jedis jedis = jedisPool.getResource()) {
byte[] data = jedis.get(key.getBytes());
if (data == null) return null;
return deserialize(data, clazz);
}
}
private byte[] serialize(Object obj) {
// 使用JSON或Java序列化
// return objectMapper.writeValueAsBytes(obj);
throw new UnsupportedOperationException("需要实现序列化");
}
private <T> T deserialize(byte[] data, Class<T> clazz) {
// 反序列化
// return objectMapper.readValue(data, clazz);
throw new UnsupportedOperationException("需要实现反序列化");
}
}
Spring Cache抽象
使用Spring的缓存注解:
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
@CacheConfig(cacheNames = "users")
public class UserService {
@Cacheable(key = "#userId", unless = "#result == null")
public User getUserById(String userId) {
// 第一次调用会执行该方法,后续直接从缓存获取
return findUserFromDatabase(userId);
}
@CachePut(key = "#user.id")
public User updateUser(User user) {
// 更新数据库并刷新缓存
updateUserInDatabase(user);
return user;
}
@CacheEvict(key = "#userId")
public void deleteUser(String userId) {
// 删除用户并清除缓存
deleteUserFromDatabase(userId);
}
@CacheEvict(allEntries = true)
public void clearAllUsers() {
// 清空所有用户缓存
}
// 配置缓存实现(在配置中指定)
@Configuration
@EnableCaching
public static class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES));
return cacheManager;
}
}
}
选择建议
| 场景 | 推荐方案 |
|---|---|
| 简单单机应用 | Guava Cache 或 Caffeine |
| 高性能单机应用 | Caffeine (性能最优) |
| 分布式环境 | Redis / Memcached |
| Spring项目 | Spring Cache + Caffeine |
| 需要持久化 | Ehcache |
最佳实践
- 设置合理的过期时间:根据业务需求设计TTL
- 限制缓存大小:防止内存溢出
- 监控缓存命中率:评估缓存有效性
- 处理缓存穿透:对不存在的数据进行空值缓存
- 缓存预热:系统启动时加载热点数据
- 缓存雪崩预防:设置不同的过期时间
- 数据一致性:确保缓存和数据库的最终一致性
选择合适的缓存方案需要考虑应用场景、性能要求和部署环境,对于大多数Java应用,Caffeine和Spring Cache的组合是不错的选择。