当前位置: 首页 > 科技观察

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

时间:2023-03-21 13:57:52 科技观察

这种场景在需求分析项目中经常遇到:一个数据需要在多个地方共享,而有些数据是时效性的,自动过期。比如手机验证码发送后需要缓存,然后为了安全起见,一般需要设置一个过期时间,过期会自动过期。我们如何实现这样的功能呢?解决方案使用现有的缓存技术框架,如redis、ehcache。优点:成熟、稳定、强大;缺点,项目需要引入相应的框架,不够轻量化。如果不考虑分布,只在单线程或多线程之间缓存数据,其实完全可以自己写一个缓存工具。这是此类工具的简单实现。先上传代码:importjava.util.HashMap;importjava.util.Map;importjava.util.concurrent.*;/***@Author:lixk*@Date:2018/5/915:03*@Description:简单记忆缓存工具类*/publicclassCache{//键值对集合privatefinalstaticMapmap=newHashMap<>();//定时器线程池,用于清除过期缓存privatefinalstaticScheduledExecutorServiceexecutor=Executors.newSingleThreadScheduledExecutor();/***添加缓存**@paramkeykey*@paramdatavalue*/publicsynchronizedstaticvoidput(Stringkey,Objectdata){Cache.put(key,data,0);}/***添加缓存**@paramkeykey*@paramdatavalue*@parameexpire过期时间,单位:毫秒,0表示***long*/publicsynchronizedstaticvoidput(Stringkey,Objectdata,longexpire){//清空原来的键值对Cache.remove(key);//设置过期时间if(expire>0){Futurefuture=executor.schedule(newRunnable(){@Overridepublicvoidrun(){//过期后清空键值对synchronized(Cache.class){map.remove(key);}}},expire,时间单位。毫秒);map.put(key,newEntity(data,future));}else{//不设置过期时间map.put(key,newEntity(data,null));}}/***读取缓存**@paramkeykey*@return*/publicsynchronizedstaticObjectget(Stringkey){实体entity=map.get(key);returnentity==null?null:entity.getValue();}/***读取缓存**@paramkeykey**@paramclazz值类型*@return*/publicsynchronizedstaticTget(Stringkey,Classclazz){returnclazz.cast(Cache.get(key));}/***清除缓存**@paramkey*@return*/publicsynchronizedstaticObjectremove(Stringkey){//清除原有缓存数据Entityentity=map.remove(key);if(entity==null)returnnull;//清空原键值对timerFuturefuture=entity.getFuture();if(future!=null)future.cancel(true);returnentity.getValue();}/***查询当前缓存的键值对个数**@return*/publicsynchronizedstaticintsize(){returnmap.size();}/***缓存实体类*/privatestaticclassEntity{//keyvalueRightvalueprivateObjectvalue;//TimerFutureprivateFuturefuture;publicEntity(Objectvalue,Futurefuture){this.value=value;this.future=future;}/***getvalue**@return*/publicObjectgetValue(){returnvalue;}/***获取Future对象**@return*/publicFuturegetFuture(){returnfuture;}}}这个工具类我s主要由HashMap+timer线程池实现,map用于存储键值对数据,map的value是CacheObjectEntity的内部类,Entity包含键值对的value和生命周期定时器FutureCache类只提供了几种同步方法:put(key,value),put(key,value,expire),get(key),get(key,class),remove(key),size()。添加键值对数据时,会先调用remove()方法清除同一个key的原有数据,取消对应的定时清除任务,再向map中添加新数据,如果设置了有效时间,然后将相应的定时清除任务添加到定时器线程池中。测试importjava.util.concurrent.ExecutionException;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.Future;/***@Author:lixk*@Date:2018/5/916:40*@Description:缓存工具类test*/publicclassCacheTest{/***test**@paramargs*/publicstaticvoidmain(String[]args)throwsInterruptedException,ExecutionException{Stringkey="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("************设置过期时间**********");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("*************并发性能测试************");//创建10个线程的线程池,分10次向线程池添加1,000,000个操作ExecutorServiceexecutorService=Executors.newFixedThreadPool(10);Future[]futures=newFuture[10];/********Add********/{longstart=System.currentTimeMillis();for(intj=0;j<10;j++){futures[j]=executorService.submit(()->{for(inti=0;i<100000;i++){Cache.put(Thread.currentThread().getId()+key+i,i,300000);}});}//等待所有线程执行完毕,打印执行时间for(Futurefuture:futures){future.get();}System.out.printf("添加耗时:%dms",System.currentTimeMillis()-start);}/********查询********/{longstart=System.currentTimeMillis();for(intj=0;j<10;j++){futures[j]=executorService.submit(()->{for(inti=0;i<100000;i++){Cache.get(Thread.currentThread().getId()+key+i);}});}//等待所有线程执行完毕,打印执行时间for(Futurefuture:futures){future.get();}System.out.printf("查询耗时:%dms",System.currentTimeMillis()-start);}System.out.println("当前缓存容量:"+Cache.size());}}测试结果:************不设置过期时间************key:id,value:123key:id,value:123key:id,value:null**********设置过期时间**********key:id,value:123456key:id,value:null***********并发性能测试************添加耗时:2313ms查询耗时:335ms当前缓存容量:1000000测试程序使用10个线程的线程池来模拟并发,一共执行了一百万次添加和查询操作,时间大概两秒,性能还不错,每秒40万次并发读写应该还是可以满足大部分高并发场景的^_^