需求分析

项目中经常会遇到这种场景:一份数据需要在多处共享,有些数据还有时效性,过期自动失效。比如手机验证码,发送之后需要缓存起来,然后处于安全性考虑,一般还要设置有效期,到期自动失效。我们怎么实现这样的功能呢?

解决方案

  1. 使用现有的缓存技术框架,比如redis,ehcache。优点:成熟,稳定,功能强大;缺点,项目需要引入对应的框架,不够轻量。
  2. 如果不考虑分布式,只是在单线程或者多线程间作数据缓存,其实完全可以自己手写一个缓存工具。下面就来简单实现一个这样的工具。

代码

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;/*** @Author: lixk* @Date: 2018/5/9 15:03* @Description: 简单的内存缓存工具类*/
public class Cache {/*** 键值对集合*/private final static Map<String, Entity> map = new HashMap<>();/*** 定时器线程池,用于清除过期缓存*/private final static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();/*** 添加缓存** @param key  键* @param data 值*/public synchronized static void put(String key, Object data) {Cache.put(key, data, 0);}/*** 添加缓存** @param key    键* @param data   值* @param expire 过期时间,单位:毫秒, 0表示无限长*/public synchronized static void put(String key, Object data, long expire) {//清除原键值对Cache.remove(key);//设置过期时间if (expire > 0) {Future future = executor.schedule(new Runnable() {@Overridepublic void run() {//过期后清除该键值对synchronized (Cache.class) {map.remove(key);}}}, expire, TimeUnit.MILLISECONDS);map.put(key, new Entity(data, future));} else {//不设置过期时间map.put(key, new Entity(data, null));}}/*** 读取缓存** @param key 键* @return*/public synchronized static <T> T get(String key) {Entity entity = map.get(key);return entity == null ? null : (T) entity.value;}/*** 清除缓存** @param key 键* @return*/public synchronized static <T> T remove(String key) {//清除原缓存数据Entity entity = map.remove(key);if (entity == null) {return null;}//清除原键值对定时器if (entity.future != null) {entity.future.cancel(true);}return (T) entity.value;}/*** 查询当前缓存的键值对数量** @return*/public synchronized static int size() {return map.size();}/*** 缓存实体类*/private static class Entity {/*** 键值对的value*/public Object value;/*** 定时器Future*/public Future future;public Entity(Object value, Future future) {this.value = value;this.future = future;}}
}

说明

本工具类主要采用HashMap+定时器线程池实现,map用于存储键值对数据,map的value是Cache的内部类对象Entity,Entity包含value和该键值对的生命周期定时器Future。Cache类对外只提供了几个同步方法:

方法 作用
put(key, value) 插入缓存数据
put(key, value, expire) 插入带过期时间的缓存数据, expire: 过期时间,单位:毫秒
get(key) 获取缓存数据
remove(key) 删除缓存数据
size() 查询当前缓存记录数

当添加键值对数据的时候,首先会调用remove()方法,清除掉原来相同key的数据,并取消对应的定时清除任务,然后添加新数据到map中,并且,如果设置了有效时间,则添加对应的定时清除任务到定时器线程池。

测试

测试代码如下:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;/*** @Author: lixk* @Date: 2018/5/9 16:40* @Description: 缓存工具类测试*/
public class CacheTest {/*** 测试** @param args*/public static void main(String[] args) throws InterruptedException {String key = "id";//不设置过期时间System.out.println("***********不设置过期时间**********");Cache.put(key, 123);System.out.println("key:" + key + ", value:" + Cache.get(key));System.out.println("key:" + key + ", value:" + Cache.remove(key));System.out.println("key:" + key + ", value:" + Cache.get(key));//设置过期时间System.out.println("\n***********设置过期时间**********");Cache.put(key, "123456", 1000);System.out.println("key:" + key + ", value:" + Cache.get(key));Thread.sleep(2000);System.out.println("key:" + key + ", value:" + Cache.get(key));System.out.println("\n***********100w读写性能测试************");//创建有10个线程的线程池,将1000000次操作分10次添加到线程池int threads = 10;ExecutorService pool = Executors.newFixedThreadPool(threads);//每批操作数量int batchSize = 100000;//添加{CountDownLatch latch = new CountDownLatch(threads);AtomicInteger n = new AtomicInteger(0);long start = System.currentTimeMillis();for (int t = 0; t < threads; t++) {pool.submit(() -> {for (int i = 0; i < batchSize; i++) {int value = n.incrementAndGet();Cache.put(key + value, value, 300000);}latch.countDown();});}//等待全部线程执行完成,打印执行时间latch.await();System.out.printf("添加耗时:%dms\n", System.currentTimeMillis() - start);}//查询{CountDownLatch latch = new CountDownLatch(threads);AtomicInteger n = new AtomicInteger(0);long start = System.currentTimeMillis();for (int t = 0; t < threads; t++) {pool.submit(() -> {for (int i = 0; i < batchSize; i++) {int value = n.incrementAndGet();Cache.get(key + value);}latch.countDown();});}//等待全部线程执行完成,打印执行时间latch.await();System.out.printf("查询耗时:%dms\n", System.currentTimeMillis() - start);}System.out.println("当前缓存容量:" + Cache.size());}
}

测试结果

***********不设置过期时间**********
key:id, value:123
key:id, value:123
key:id, value:null***********设置过期时间**********
key:id, value:123456
key:id, value:null***********100w读写性能测试************
添加耗时:1729ms
查询耗时:283ms
当前缓存容量:1000000

测试程序使用有10个线程的线程池来模拟并发,分别执行一百万次添加和查询操作,时间大约在两秒左右,表现还不错,每秒近百万读写应该还是可以满足大多数高并发场景的 ^^


备注:如果对缓存失效延迟非常敏感,不能容忍百万数据亚秒级失效延迟,必须保证严格失效时间的话,可以参考另一版实现(数据实体加入了过期时间,每次取出数据时会先做判断)。地址:https://github.com/lixk/ECache

自己实现简单Java缓存类相关推荐

  1. 缓存类java_用Java写一个简单的缓存操作类

    前言 使用缓存已经是开发中老生常谈的一件事了,常用专门处理缓存的工具比如Redis.MemCache等,但是有些时候可能需要一些简单的缓存处理,没必要用上这种专门的缓存工具,那么自己写一个缓存类最合适 ...

  2. Map实现java缓存机制的简单实例

    缓存是Java中主要的内容,主要目的是缓解项目访问数据库的压力以及提升访问数据的效率,以下是通过Map实现java缓存的功能,并没有用cache相关框架. 一.缓存管理类 CacheMgr.java ...

  3. java代码中的缓存类怎么找,JAVA缓存的实现 - dreamcloudz的个人空间 - OSCHINA - 中文开源技术交流社区...

    缓存可分为二大类: 一.通过文件缓存,顾名思义文件缓存是指把数据存储在磁盘上,不管你是以XML格式,序列化文件DAT格式还是其它文件格式: 二.内存缓存,也就是实现一个类中静态Map,对这个Map进行 ...

  4. java 缓存方法_Java实现一个简单的缓存方法

    Java实现一个简单的缓存方法 发布时间:2020-09-07 21:39:55 来源:脚本之家 阅读:99 作者:BrightLoong 缓存是在web开发中经常用到的,将程序经常使用到或调用到的对 ...

  5. java 缓存的简单实现

    实现一个简单的缓存,具有缓存添加,获取,删除,过期自动删除等,主要存储方式为Map 首先建一个缓存数据实体类,用到了lombok消除get set 和全参构造函数 import lombok.AllA ...

  6. java做简单的缓存缓存

    java做简单的缓存缓存 不说废话直接上代码 1.缓存类 package com.common.util; import java.util.HashMap; import java.util.Map ...

  7. java 数据对_数据表与简单java类(一对多)

    emp表:empno,ename,job,sal,comm,mgr,deptno dept表:deptno,dname,loc 要求可以通过程序描述出如下对应关系 一个部门有多个部员,并且可以输出一个 ...

  8. java引用其他类的数据头文件_Java 实现数据表与简单Java类映射转换

    我们在程序开发过程中往往会使用简单Java类进行数据表结构的描述,本文主要介绍如何简单Java类与数据表之间的转换. 首先,先简单介绍一下数据表与简单Java类的相关概念对比: 表的定义 → 实体表设 ...

  9. java通用解析excel_java读取简单excel通用工具类

    本文实例为大家分享了java读取简单excel通用工具类的具体代码,供大家参考,具体内容如下 读取excel通用工具类 import java.io.File; import java.io.File ...

  10. 你连简单的枚举类都不知道,还敢说自己会Java???滚出我的公司

    枚举类型是Java 5中新增的特性,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性.安全性以及便捷性.当 ...

最新文章

  1. 赠书 | 联邦学习如何在视觉领域应用?
  2. 北美欧洲顶级大咖齐聚,在这里读懂 AIoT 未来!
  3. 滴答定时器的计数模式_SysTick(系统滴答定时器)
  4. java mongodb 使用MongoCollection,BasicDBObject 条件查询
  5. Android适配器以及作用,Android RecyclerView 通用适配器的实现
  6. java制作带有logo的二维码,解决zxing中文乱码
  7. WebUserControl归纳
  8. CentOS 7安装Hadoop 3.0.0
  9. 51单片机学习笔记之定时器程序设计
  10. LeetCode 205. 同构字符串(哈希map)
  11. MySQL保留关键字
  12. Spring高级之注解@PropertySource详解(超详细)
  13. IDM下载百度资源出现403的解决方法
  14. spring cloud 总结(摘抄版)
  15. 【Linux】scp命令基本使用
  16. 面试官:请你谈谈ConcurrentHashMap
  17. 小福利,用selenium模块爬取qq音乐歌单!
  18. Unity游戏帧同步技术分享篇【01】帧同步解决方案概述
  19. Leetcode 1905. Count Sub Islands [Python]
  20. html文件用wps打开,wps文件如何打开?直接打开wps文件的操作技巧

热门文章

  1. Matlab/Python两方,三方甚至四方演化博弈仿真图及 代码 演化博弈敏感性分析仿真图及相轨迹图/相位图及代码
  2. 数字电路技术可能出现的简答题_数字电子技术基础期末考试题
  3. java nekohtml_用过nekohtml的进来
  4. Charles使用手册
  5. java adt教程_用Eclipse安装ADT插件搭建Android环境(图文)
  6. Win10家庭版开启远程桌面
  7. window平台使用网络抓包工具wireshark打开后卡死崩溃的解决
  8. 编程语言习题集(1)
  9. 移动通信原理B-------例题解答2
  10. 弹出窗口背景透明 css,CSS弹出背景半透明窗口