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使用ConcurrentHashMap实现简单的内存式缓存的更多相关文章
- Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC
[转载] :http://my.oschina.net/rouchongzi/blog/171046 Java之类加载机制 类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指 ...
- java与C#的简单比较
刚刚看完java视频,做了个简单图: 新知识不多,大多是与以往知识的相互碰撞,一下做了java与C#的简单比较: Java C# 主类名与文件名 必须一 ...
- java拾遗4----一个简单java程序的运行全过程
简单说来,一个java程序的运行需要编辑源码.编译生成class文件.加载class文件.解释或编译运行class中的字节码指令. 下面有一段简单的java源码,通过它来看一下java程序的运行流程: ...
- Java中的垃圾回收机制&内存管理&内存泄漏
1. Java在创建对象时,会自动分配内存,并当该对象引用不存在的时候,释放这块内存. 为什么呢? 因为Java中使用被称为垃圾收集器的技术来监视Java程序的运行,当对象不再使用时,就自动释放对象所 ...
- Java:ConcurrentHashMap类小记-1(概述)
Java:ConcurrentHashMap类小记-1(概述) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentHas ...
- 【转载】java项目中经常碰到的内存溢出问题: java.lang.OutOfMemoryError: PermGen space, 堆内存和非堆内存,写的很好,理解很方便
Tomcat Xms Xmx PermSize MaxPermSize 区别 及 java.lang.OutOfMemoryError: PermGen space 解决 解决方案 在 catalin ...
- 一个Java对象到底占用多大内存?
最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...
- 一个Java对象到底占用多大内存
在网上搜到了一篇博客讲的非常好,里面提供的这个类也非常实用: import java.lang.instrument.Instrumentation; import java.lang.reflect ...
- 一个Java对象到底占多大内存
最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...
- 一个Java对象到底占多大内存?(转)
最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...
随机推荐
- 代码使我头疼之React初学习
前言 开始了,去年(2020)说要学的React,到现在2021年的12月底了,才来实施--(年底警告!年末总结还没开始写!) 不过前端为啥要学React呢?Vue不是很好用吗?Vue确实很好用,并且 ...
- [转帖]HBase实战:记一次Safepoint导致长时间STW的踩坑之旅
https://mp.weixin.qq.com/s/GEwD1B-XqFIudWP_EbGgdQ 过 程 记 录 现象:小米有一个比较大的公共离线HBase集群,用户很多,每天有大量的Ma ...
- [转帖]Linux下使用 ipset 封大量IP及ipset参数说明
https://www.cnblogs.com/xiaofeng666/p/10952627.html Linux使用iptables封IP,是常用的应对网络攻击的方法,但要封禁成千上万个IP,如果添 ...
- Unity下调试ToLua(基于IDEA和VSCode)
公司移动端项目是基于Unity的,底层支持由C#提供,上层Lua调用C#中注册的函数支持来做业务逻辑,框架用的是ToLua.开始做移动端有一段时间了,一直都觉得调试代码是个很蛋疼的体验:几乎都是靠肉眼 ...
- 离开页面关闭video标签
<video src="./play.mp4" id="maskmore_1" controls="controls" autopla ...
- Gin CORS 跨域请求资源共享与中间件
Gin CORS 跨域请求资源共享与中间件 目录 Gin CORS 跨域请求资源共享与中间件 一.同源策略 1.1 什么是浏览器的同源策略? 1.2 同源策略判依据 1.3 跨域问题三种解决方案 二. ...
- 关于git pull机制和游戏开发热更新思考
前言 今天由于网速很慢,在git pull更新时我观看了git pull的日志,让我联想到和我现在从事的游戏开发中的热更热有一定的相似性,把思绪记录下来. git pull 日志 使用tortoi ...
- UIE_Slim满足工业应用场景,解决推理部署耗时问题,提升效能。
项目链接:fork一下即可 UIE Slim满足工业应用场景,解决推理部署耗时问题,提升效能! 如果有图片缺失查看原项目 UIE Slim满足工业应用场景,解决推理部署耗时问题,提升效能 在UIE强大 ...
- 深度学习应用篇-自然语言处理[10]:N-Gram、SimCSE介绍,更多技术:数据增强、智能标注、多分类算法、文本信息抽取、多模态信息抽取、模型压缩算法等
深度学习应用篇-自然语言处理[10]:N-Gram.SimCSE介绍,更多技术:数据增强.智能标注.多分类算法.文本信息抽取.多模态信息抽取.模型压缩算法等 1.N-Gram N-Gram是一种基于统 ...
- 5.10 Windows驱动开发:摘除InlineHook内核钩子
在笔者上一篇文章<内核层InlineHook挂钩函数>中介绍了通过替换函数头部代码的方式实现Hook挂钩,对于ARK工具来说实现扫描与摘除InlineHook钩子也是最基本的功能,此类功能 ...