需求说明:

实际项目中我打算把用户和组织信息放到缓存中,基于此提出以下几点需求:

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实现简单的内存式缓存的更多相关文章

  1. Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC

    [转载] :http://my.oschina.net/rouchongzi/blog/171046 Java之类加载机制 类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指 ...

  2. java与C#的简单比较

    刚刚看完java视频,做了个简单图: 新知识不多,大多是与以往知识的相互碰撞,一下做了java与C#的简单比较:                         Java C# 主类名与文件名 必须一 ...

  3. java拾遗4----一个简单java程序的运行全过程

    简单说来,一个java程序的运行需要编辑源码.编译生成class文件.加载class文件.解释或编译运行class中的字节码指令. 下面有一段简单的java源码,通过它来看一下java程序的运行流程: ...

  4. Java中的垃圾回收机制&内存管理&内存泄漏

    1. Java在创建对象时,会自动分配内存,并当该对象引用不存在的时候,释放这块内存. 为什么呢? 因为Java中使用被称为垃圾收集器的技术来监视Java程序的运行,当对象不再使用时,就自动释放对象所 ...

  5. Java:ConcurrentHashMap类小记-1(概述)

    Java:ConcurrentHashMap类小记-1(概述) 对 Java 中的 ConcurrentHashMap类,做一个微不足道的小小小小记,分三篇博客: Java:ConcurrentHas ...

  6. 【转载】java项目中经常碰到的内存溢出问题: java.lang.OutOfMemoryError: PermGen space, 堆内存和非堆内存,写的很好,理解很方便

    Tomcat Xms Xmx PermSize MaxPermSize 区别 及 java.lang.OutOfMemoryError: PermGen space 解决 解决方案 在 catalin ...

  7. 一个Java对象到底占用多大内存?

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  8. 一个Java对象到底占用多大内存

    在网上搜到了一篇博客讲的非常好,里面提供的这个类也非常实用: import java.lang.instrument.Instrumentation; import java.lang.reflect ...

  9. 一个Java对象到底占多大内存

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

  10. 一个Java对象到底占多大内存?(转)

    最近在读<深入理解Java虚拟机>,对Java对象的内存布局有了进一步的认识,于是脑子里自然而然就有一个很普通的问题,就是一个Java对象到底占用多大内存? 在网上搜到了一篇博客讲的非常好 ...

随机推荐

  1. Net Core中使用EF Core连接Mysql数据库

    Entity Framework Core的前身是微软提供并主推的ORM框架,简称EF,其底层是对ADO.NET的封装.EF支持SQLServer.MYSQL.Oracle.Sqlite等所有主流数据 ...

  2. PG数据库存储验证

    PG数据库存储验证 背景 最近学习了SQLServer数据库的varchar和nvarchar的存储 想到PG数据库其实没让选择字符集,也没有nvarchar 所以想学习一下nvarchar的使用情况 ...

  3. [转帖]linux中批量多行缩进与添加空格

    用vim打开修改python脚本的时候,将代码整体向后移动4个空格操作如下: ESC之后,ctrl+v进入多行行首选中模式 使用上下键进行上下移动,选中多行行首 shift+i,进入插入模式 连续敲击 ...

  4. [转帖]《Linux性能优化实战》笔记(五)—— 不可中断进程与僵尸进程

    一. 进程状态 1. 状态含义 从 ps或者 top 命令的输出中,可以看到处于不同状态的进程 R:Running 或 Runnable,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行 D ...

  5. [转帖]Jmeter学习笔记(二十一)——Concurrency Thread Group阶梯式加压测试

    https://www.cnblogs.com/pachongshangdexuebi/p/11739064.html 一.先安装jmeter的插件管理工具 1.下载地址:jmeter-plugins ...

  6. [转帖]jmeter之发送jdbc请求--06篇

    1.setup线程组中新建一个JDBC Connection Configuration配置元件 2.设置配置信息 Database URL:jdbc:mysql://127.0.0.1:3306/v ...

  7. [转帖]88. sys_kwr

    88. sys_kwr ¶ 88.1. 插件sys_kwr简介 ¶ 插件sys_kwr是KingbaseES 的一个扩展插件.主要功能是通过周期性自动记录性能统计相关的快照,分析出KingbaseES ...

  8. RPM安装的Oracle19c 修改init.ora进行修复以及最简单开机启动Oracle的方法

    RPM安装的Oracle19c 修改init.ora进行修复以及最简单开机启动Oracle的方法 背景 今天开始使用自己心的ThinkBook14 的笔记本 因为已经没有了 Linux测试环境供我使用 ...

  9. SQLServer Core 序列号使用CPU限制的处理

    SQLServer Core 序列号使用CPU限制的处理 背景 有客户是SQLSERVER的数据库. 说要进行一下压测. 这边趁着最后进行一下环境的基础搭建工作. 然后在全闪的环境上面搭建了一个Win ...

  10. Kafka学习之四_Grafana监控相关的学习

    Kafka学习之四_Grafana监控相关的学习 背景 想一并学习一下kafaka的监控. 又重新开始学习grafana了: 下载地址: https://grafana.com/grafana/dow ...