面试官:你是如何使用JDK来实现自己的缓存(支持高并发)?

需求分析

创新互联自成立以来,一直致力于为企业提供从网站策划、网站设计、网站建设、成都做网站、电子商务、网站推广、网站优化到为企业提供个性化软件开发等基于互联网的全面整合营销服务。公司拥有丰富的网站建设和互联网应用系统开发管理经验、成熟的应用系统解决方案、优秀的网站开发工程师团队及专业的网站设计师团队。

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

解决方案

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

先上代码: 

 
 
 
  1. import java.util.HashMap;  
  2. import java.util.Map;  
  3. import java.util.concurrent.*;  
  4. /**  
  5.  * @Author: lixk  
  6.  * @Date: 2018/5/9 15:03  
  7.  * @Description: 简单的内存缓存工具类  
  8.  */  
  9. public class Cache {  
  10.  //键值对集合  
  11.  private final static Map map = new HashMap<>();  
  12.  //定时器线程池,用于清除过期缓存  
  13.  private final static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();  
  14.  /**  
  15.  * 添加缓存  
  16.  *  
  17.  * @param key 键  
  18.  * @param data 值  
  19.  */  
  20.  public synchronized static void put(String key, Object data) {  
  21.  Cache.put(key, data, 0);  
  22.  }  
  23.  /**  
  24.  * 添加缓存  
  25.  *  
  26.  * @param key 键  
  27.  * @param data 值  
  28.  * @param expire 过期时间,单位:毫秒, 0表示***长  
  29.  */  
  30.  public synchronized static void put(String key, Object data, long expire) {  
  31.  //清除原键值对  
  32.  Cache.remove(key);  
  33.  //设置过期时间  
  34.  if (expire > 0) {  
  35.  Future future = executor.schedule(new Runnable() {  
  36.  @Override  
  37.  public void run() {  
  38.  //过期后清除该键值对  
  39.  synchronized (Cache.class) {  
  40.  map.remove(key);  
  41.  }  
  42.  }  
  43.  }, expire, TimeUnit.MILLISECONDS);  
  44.  map.put(key, new Entity(data, future));  
  45.  } else {  
  46.  //不设置过期时间  
  47.  map.put(key, new Entity(data, null));  
  48.  }  
  49.  }  
  50.  /**  
  51.  * 读取缓存  
  52.  *  
  53.  * @param key 键  
  54.  * @return  
  55.  */  
  56.  public synchronized static Object get(String key) {  
  57.  Entity entity = map.get(key);  
  58.  return entity == null ? null : entity.getValue();  
  59.  }  
  60.  /**  
  61.  * 读取缓存  
  62.  *  
  63.  * @param key 键  
  64.  * * @param clazz 值类型  
  65.  * @return  
  66.  */  
  67.  public synchronized static  T get(String key, Class clazz) {  
  68.  return clazz.cast(Cache.get(key));  
  69.  }  
  70.  /**  
  71.  * 清除缓存  
  72.  *  
  73.  * @param key  
  74.  * @return  
  75.  */  
  76.  public synchronized static Object remove(String key) {  
  77.  //清除原缓存数据  
  78.  Entity entity = map.remove(key);  
  79.  if (entity == null) return null;  
  80.  //清除原键值对定时器  
  81.  Future future = entity.getFuture();  
  82.  if (future != null) future.cancel(true);  
  83.  return entity.getValue();  
  84.  }  
  85.  /**  
  86.  * 查询当前缓存的键值对数量  
  87.  *  
  88.  * @return  
  89.  */  
  90.  public synchronized static int size() {  
  91.  return map.size();  
  92.  }  
  93.  /**  
  94.  * 缓存实体类  
  95.  */  
  96.  private static class Entity {  
  97.  //键值对的value  
  98.  private Object value;  
  99.  //定时器Future   
  100.  private Future future;  
  101.  public Entity(Object value, Future future) {  
  102.  this.value = value;  
  103.  this.future = future;  
  104.  }  
  105.  /**  
  106.  * 获取值  
  107.  *  
  108.  * @return  
  109.  */  
  110.  public Object getValue() {  
  111.  return value;  
  112.  }  
  113.  /**  
  114.  * 获取Future对象  
  115.  *  
  116.  * @return  
  117.  */  
  118.  public Future getFuture() {  
  119.  return future;  
  120.  }  
  121.  }  

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

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

测试 

 
 
 
  1. import java.util.concurrent.ExecutionException;  
  2. import java.util.concurrent.ExecutorService;  
  3. import java.util.concurrent.Executors;  
  4. import java.util.concurrent.Future;  
  5. /**  
  6.  * @Author: lixk  
  7.  * @Date: 2018/5/9 16:40  
  8.  * @Description: 缓存工具类测试  
  9.  */  
  10. public class CacheTest {  
  11.  /**  
  12.  * 测试  
  13.  *  
  14.  * @param args  
  15.  */  
  16.  public static void main(String[] args) throws InterruptedException, ExecutionException {  
  17.  String key = "id";  
  18.  //不设置过期时间  
  19.  System.out.println("***********不设置过期时间**********");  
  20.  Cache.put(key, 123);  
  21.  System.out.println("key:" + key + ", value:" + Cache.get(key));  
  22.  System.out.println("key:" + key + ", value:" + Cache.remove(key));  
  23.  System.out.println("key:" + key + ", value:" + Cache.get(key));  
  24.  //设置过期时间  
  25.  System.out.println("  
  26. ***********设置过期时间**********");  
  27.  Cache.put(key, "123456", 1000);  
  28.  System.out.println("key:" + key + ", value:" + Cache.get(key));  
  29.  Thread.sleep(2000);  
  30.  System.out.println("key:" + key + ", value:" + Cache.get(key));  
  31.  /******************并发性能测试************/  
  32.  System.out.println("  
  33. ***********并发性能测试************");  
  34.  //创建有10个线程的线程池,将1000000次操作分10次添加到线程池  
  35.  ExecutorService executorService = Executors.newFixedThreadPool(10);  
  36.  Future[] futures = new Future[10];  
  37.  /********添加********/  
  38.  {  
  39.  long start = System.currentTimeMillis();  
  40.  for (int j = 0; j < 10; j++) {  
  41.  futures[j] = executorService.submit(() -> {  
  42.  for (int i = 0; i < 100000; i++) {  
  43.  Cache.put(Thread.currentThread().getId() + key + i, i, 300000);  
  44.  }  
  45.  });  
  46.  }  
  47.  //等待全部线程执行完成,打印执行时间  
  48.  for (Future future : futures) {  
  49.  future.get();  
  50.  }  
  51.  System.out.printf("添加耗时:%dms  
  52. ", System.currentTimeMillis() - start);  
  53.  }  
  54.  /********查询********/  
  55.  {  
  56.  long start = System.currentTimeMillis();  
  57.  for (int j = 0; j < 10; j++) {  
  58.  futures[j] = executorService.submit(() -> {  
  59.  for (int i = 0; i < 100000; i++) {  
  60.  Cache.get(Thread.currentThread().getId() + key + i);  
  61.  }  
  62.  });  
  63.  }  
  64.  //等待全部线程执行完成,打印执行时间  
  65.  for (Future future : futures) {  
  66.  future.get();  
  67.  }  
  68.  System.out.printf("查询耗时:%dms  
  69. ", System.currentTimeMillis() - start);  
  70.  }  
  71.  System.out.println("当前缓存容量:" + Cache.size());  
  72.  }  

测试结果: 

 
 
 
  1. ***********不设置过期时间**********  
  2. key:id, value:123  
  3. key:id, value:123  
  4. key:id, value:null  
  5. ***********设置过期时间**********  
  6. key:id, value:123456  
  7. key:id, value:null  
  8. ***********并发性能测试************  
  9. 添加耗时:2313ms  
  10. 查询耗时:335ms  
  11. 当前缓存容量:1000000 

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

文章标题:面试官:你是如何使用JDK来实现自己的缓存(支持高并发)?
标题链接:http://www.gawzjz.com/qtweb/news7/204107.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联