Java使用ConcurrentHashMap实现简单的内存式缓存
需求说明:
实际项目中我打算把用户和组织信息放到缓存中,基于此提出以下几点需求:
1.数据存储在内存中;
2.允许以键值对的方式存储对象类数据并带有过期策略;
3.不限制内存使用,但cache也不能给我报出OutOfMemoryErrormemory异常;
4.cache要自动清理过期对象
5.线程安全
为了满足以上需求,本例子中主要使用了以下技术:
1.ConcurrentHashMap
2.DelayQueue
3.SoftReference
4.Thread
5.java8的Optional及函数式编程
这里提供2中编码实现,先定义一个接口,用于规范和统一方法:
package com.dylan.springboot.helloweb.cache;
/**
* @Description: 自定义缓存接口
* @Author laoxu
* @Date 2019/7/27 13:45
**/
public interface ICache {
void add(String key, Object value, long periodInMillis);
void remove(String key);
Object get(String key);
void clear();
long size();
}
方案1:
package com.dylan.springboot.helloweb.cache;
/**
* @Description: 带过期时间的缓存对象
* @Author laoxu
* @Date 2019/7/27 14:27
**/
public class CacheObject {
private Object value;
private long expiryTime;
public CacheObject(Object value, long expiryTime) {
this.value = value;
this.expiryTime = expiryTime;
}
public Object getValue() {
return value;
}
boolean isExpired() {
return System.currentTimeMillis() > expiryTime;
}
}
package com.dylan.springboot.helloweb.cache;
import java.lang.ref.SoftReference;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Description: 方案1,缓存实现
* @Author laoxu
* @Date 2019/7/27 13:47
**/
// @Component
public class CacheImpl implements ICache {
private static final int CLEAN_UP_PERIOD_IN_SEC = 5;
private final ConcurrentHashMap<String, SoftReference<CacheObject>> cache = new ConcurrentHashMap<>();
public CacheImpl() {
Thread cleanerThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(CLEAN_UP_PERIOD_IN_SEC * 1000);
cache.entrySet().removeIf(entry -> Optional.ofNullable(entry.getValue()).map(SoftReference::get).map(CacheObject::isExpired).orElse(false));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
cleanerThread.setDaemon(true);
cleanerThread.start();
}
@Override
public void add(String key, Object value, long periodInMillis) {
if (key == null) {
return;
}
if (value == null) {
cache.remove(key);
} else {
long expiryTime = System.currentTimeMillis() + periodInMillis;
cache.put(key, new SoftReference<>(new CacheObject(value, expiryTime)));
}
}
@Override
public void remove(String key) {
cache.remove(key);
}
@Override
public Object get(String key) {
return Optional.ofNullable(cache.get(key)).map(SoftReference::get).filter(cacheObject -> !cacheObject.isExpired()).map(CacheObject::getValue).orElse(null);
}
@Override
public void clear() {
cache.clear();
}
@Override
public long size() {
return cache.entrySet().stream().filter(entry -> Optional.ofNullable(entry.getValue()).map(SoftReference::get).map(cacheObject -> !cacheObject.isExpired()).orElse(false)).count();
}
}
此方案有2大缺陷:
1.如果map中存储了大量的对象那么扫描这些对象并清理需要花不少时间;
2.此处的size()方法将花费O(n)时间因为它必须过滤掉过期对象。
方案2(推荐):
package com.dylan.springboot.helloweb.cache;
import java.lang.ref.SoftReference;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
/**
* @Description: 带key和过期时间的缓存对象
* @Author laoxu
* @Date 2019/7/27 15:28
**/
public class DelayedCacheObject implements Delayed {
private final String key;
private final SoftReference<Object> reference;
private final long expiryTime;
public DelayedCacheObject(String key, SoftReference<Object> reference, long expiryTime) {
this.key = key;
this.reference = reference;
this.expiryTime = expiryTime;
}
public String getKey() {
return key;
}
public SoftReference<Object> getReference() {
return reference;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(expiryTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(expiryTime, ((DelayedCacheObject) o).expiryTime);
}
}
package com.dylan.springboot.helloweb.cache;
import org.springframework.stereotype.Component;
import java.lang.ref.SoftReference;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.DelayQueue;
/**
* @Description: 方案2,缓存实现
* @Author laoxu
* @Date 2019/7/27 15:24
**/
@Component
public class InMemoryCacheWithDelayQueue implements ICache {
private final ConcurrentHashMap<String, SoftReference<Object>> cache = new ConcurrentHashMap<>();
private final DelayQueue<DelayedCacheObject> cleaningUpQueue = new DelayQueue<>();
public InMemoryCacheWithDelayQueue() {
Thread cleanerThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
DelayedCacheObject delayedCacheObject = cleaningUpQueue.take();
cache.remove(delayedCacheObject.getKey(), delayedCacheObject.getReference());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
cleanerThread.setDaemon(true);
cleanerThread.start();
}
@Override
public void add(String key, Object value, long periodInMillis) {
if (key == null) {
return;
}
if (value == null) {
cache.remove(key);
} else {
long expiryTime = System.currentTimeMillis() + periodInMillis;
SoftReference<Object> reference = new SoftReference<>(value);
cache.put(key, reference);
cleaningUpQueue.put(new DelayedCacheObject(key, reference, expiryTime));
}
}
@Override
public void remove(String key) {
cache.remove(key);
}
@Override
public Object get(String key) {
return Optional.ofNullable(cache.get(key)).map(SoftReference::get).orElse(null);
}
@Override
public void clear() {
cache.clear();
}
@Override
public long size() {
return cache.size();
}
}
测试
为了方便测试,我在spring boot中添加了controller:
package com.dylan.springboot.helloweb.controller;
import com.dylan.springboot.helloweb.cache.ICache;
import com.dylan.springboot.helloweb.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @Description: 测试缓存
* @Author laoxu
* @Date 2019/7/27 14:35
**/
@RestController
@RequestMapping("/cache")
public class CacheController {
@Autowired
private ICache cache;
@GetMapping("/size")
public Long getSize(){
return cache.size();
}
@PostMapping("/add")
public String add(@RequestBody Student student){
cache.add(student.getName(),student,60000);
return "success";
}
@GetMapping("/get")
public Student get(String name){
Student student = (Student) cache.get(name);
return student;
}
}
相关测试截图:
添加:
查询
1分钟内查看大小
1分钟后查看大小
分类:
Java-Core
标签:
ConcurrentHashMap缓存
, java缓存
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?