艺多不压身 -- 常用缓存Cache机制的实现
常用缓存Cache机制的实现
缓存,就是将程序或系统经常要调用的对象存在内存中,以便其使用时可以快速调用,不必再去创建新的重复的实例。 这样做可以减少系统开销,提高系统效率。
缓存主要可分为二大类:
一、通过文件缓存,顾名思义文件缓存是指把数据存储在磁盘上,不管你是以XML格式,序列化文件DAT格式还是其它文件格式
二、内存缓存,也就是实现一个类中静态Map,对这个Map进行常规的增删查.
Java实现cache的基本机制
我这里说的cache不是指CPU和RAM之间的缓存,而是Java应用中间常用的缓存。最常使用的场合就是访问数据库的时候为了提高效率而使用的 cache。一般的用法就是把数据从数据库读到内存,然后之后的数据访问都从内存来读,从而减少对数据库的读取次数来提高效率。
在使用cache的时候最容易犯的错误就是cache涉及了业务逻辑。使用cache的原意是只是提高程序效率,而不应该干涉程序结果。按照cahce的定义,cache应该是对数据访问端透明 地工作。所以在使用cache的时候我们可以问一下自己:“我把cache拿掉后程序还能运行吗?” “cache拿掉前后程序运行的结果一直吗?”。如果答案是否,那您就得重新考虑您的cache方案。常见bug:数据库的有个表里面都 是些配置信息,也就是说是些读访问远大于写访问 的数据。然后这些数据被理所应当地在程序里面做成内存 cache。问题是有个delete方法删除了一条数据,但是没有更新内存cache。所以读操作的客户代码还是能读到这条数据。问题的根本就是后台数据和cache不一致。
cache的容量一般相对后台数据量都比较有限。一旦cache满了就势必要选择最没用的数据从cache里面删除掉,为新数据腾出空间。这里就涉及 cahce算法cache algorithm或者叫替换算法。在java的cache产品中一般叫evict policy。下面我们来看一下常用的cache algorithm。
- 最近最少使用算法 Least Recently Used (LRU):
- First In, First Out算法
- 最近最多时用算法Most Recently Used (MRU)
- 使用次数最小算法 Least Frequently Used (LFU)
另外还有其他的cache算法,譬如按照元素自带的过期值expiration和随机random来evict元素的算法。在真正的cache产品中数据结构和算法要比上面描述的要复杂。有些产品自己定义一些数据结构来提高效率,毕竟cache是为了提高效率而产生的。高级的cache产品还可能包括事务机制,JMX和支持cluster环境这样复杂的特性。
自己动手实现java中cache
实现思路:
创建一个静态Hashtable用于保存key和value,对于cache过期后的方法回调,在cache过期后,再访问cache的时候进行,避免了使用定时器轮询过期时间,进行cache清除的效率损耗。
使用synchronized关键字进行多线程同步。
包括二个类和一个接口:
cache类:里面都是静态方法,提供基于key,value的方法进行cache的添加,修改,访问,进行cache过期后调用callback方法。
cacheitem类:用于管理每个条目的cache内容和超时时间回调方法
ICacheMethod接口:cache到期回调方法需要实现的接口
cache类:里面都是静态方法
package limeCache; import java.util.Date; /** * 静态方法,提供基于key,value的方法进行cache的添加,修改,访问,进行cache过期后调用callback方法。 * * @author lime * */ public class Cache { private static java.util.Hashtable<String, Object> __cacheList = new java.util.Hashtable<String, Object>(); public Cache() { } // 添加cache,不过期 public synchronized static void add(String key, Object value) { Cache.add(key, value, -1); } // 添加cache有过期时间 public synchronized static void add(String key, Object value, long timeOut) { Cache.add(key, value, timeOut, null); } // 添加cache有过期时间并且具有回调方法 public synchronized static void add(String key, Object value, long timeOut, ICacheMethod callback) { if (timeOut > 0) { timeOut += new Date().getTime(); } CacheItem item = new CacheItem(key, value, timeOut, callback); Cache.__cacheList.put(key, item); } // 获取cache public synchronized static Object get(String key) { Object obj = Cache.__cacheList.get(key); if (obj == null) { return null; } CacheItem item = (CacheItem) obj; boolean expired = Cache.cacheExpired(key); if (expired == true) // 已过期 { if (item.getCallback() == null) { Cache.remove(key); return null; } else { ICacheMethod callback = item.getCallback(); callback.execute(key); expired = Cache.cacheExpired(key); if (expired == true) { Cache.remove(key); return null; } } } return item.getValue(); } // 移除cache public synchronized static void remove(String key) { Object obj = Cache.__cacheList.get(key); if (obj != null) { obj = null; } Cache.__cacheList.remove(key); } // 清理所有cache对象 public synchronized static void clear() { for (String s : Cache.__cacheList.keySet()) { Cache.__cacheList.put(s, null); } Cache.__cacheList.clear(); } // 判断是否过期 private static boolean cacheExpired(String key) { CacheItem item = (CacheItem) Cache.__cacheList.get(key); if (item == null) { return false; } long milisNow = new Date().getTime(); long milisExpire = item.getTimeOut(); if (milisExpire <= 0) { // 不过期 return false; } else if (milisNow >= milisExpire) { return true; } else { return false; } } }
CacheItem 类 :
package limeCache; /** * 用于管理每个条目的cache内容和超时时间回调方法 * * @author lime * */ public class CacheItem { private String key; private Object value; private long timeOut; private ICacheMethod callback = null; public CacheItem() { } public ICacheMethod getCallback() { return callback; } public void setCallback(ICacheMethod callback) { this.callback = callback; } public CacheItem(String key, Object value) { this.key = key; this.value = value; this.timeOut = 0; } public CacheItem(String key, Object value, long timeOut) { this.key = key; this.value = value; this.timeOut = timeOut; } public CacheItem(String key, Object value, long timeOut, ICacheMethod callback) { this.key = key; this.value = value; this.timeOut = timeOut; this.callback = callback; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } public long getTimeOut() { return timeOut; } public void setTimeOut(long timeOut) { this.timeOut = timeOut; } }
ICacheMethod 接口 :
package limeCache; /** * cache到期回调方法需要实现的接口 * * @author lime * */ public interface ICacheMethod { public void execute(String key); }
java cache过期策略两种实现,一个基于list轮询一个基于timer定时
最近项目要引入缓存机制,但是不想引入分布式的缓存框架,所以自己就写了一个轻量级的缓存实现,有两个版本,一个是通过timer实现其超时过期处理,另外一个是通过list轮询。
首先要了解下java1.6中的ConcurrentMap ,他是一个线程安全的Map实现,特别说明的是在没有特别需求的情况下可以用ConcurrentHashMap。我是想学习一下读写锁的应用,就自己实现了一个SimpleConcurrentHashMap.
Class : SimpleConcurrentMap
package limeCache.self; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class SimpleConcurrentMap<K, V> implements Map<K, V> { final ReadWriteLock lock = new ReentrantReadWriteLock(); final Lock r = lock.readLock(); final Lock w = lock.writeLock(); final Map<K, V> map; public SimpleConcurrentMap(Map<K, V> map) { this.map = map; if (map == null) throw new NullPointerException(); } public void clear() { w.lock(); try { map.clear(); } finally { w.unlock(); } } public boolean containsKey(Object key) { r.lock(); try { return map.containsKey(key); } finally { r.unlock(); } } public boolean containsValue(Object value) { r.lock(); try { return map.containsValue(value); } finally { r.unlock(); } } public Set<java.util.Map.Entry<K, V>> entrySet() { throw new UnsupportedOperationException(); } public V get(Object key) { r.lock(); try { return map.get(key); } finally { r.unlock(); } } public boolean isEmpty() { r.lock(); try { return map.isEmpty(); } finally { r.unlock(); } } public Set<K> keySet() { r.lock(); try { return new HashSet<K>(map.keySet()); } finally { r.unlock(); } } public V put(K key, V value) { w.lock(); try { return map.put(key, value); } finally { w.unlock(); } } public void putAll(Map<? extends K, ? extends V> m) { w.lock(); try { map.putAll(m); } finally { w.unlock(); } } public V remove(Object key) { w.lock(); try { return map.remove(key); } finally { w.unlock(); } } public int size() { r.lock(); try { return map.size(); } finally { r.unlock(); } } public Collection<V> values() { r.lock(); try { return new ArrayList<V>(map.values()); } finally { r.unlock(); } } }
缓存对象CacheEntity.Java为:
package limeCache.self; import java.io.Serializable; public class CacheEntity implements Serializable{ private static final long serialVersionUID = -3971709196436977492L; private final int DEFUALT_VALIDITY_TIME = 20;//默认过期时间 20秒 private String cacheKey; private Object cacheContext; private int validityTime;//有效期时长,单位:秒 private long timeoutStamp;//过期时间戳 private CacheEntity(){ this.timeoutStamp = System.currentTimeMillis() + DEFUALT_VALIDITY_TIME * 1000; this.validityTime = DEFUALT_VALIDITY_TIME; } public CacheEntity(String cacheKey, Object cacheContext){ this(); this.cacheKey = cacheKey; this.cacheContext = cacheContext; } public CacheEntity(String cacheKey, Object cacheContext, long timeoutStamp){ this(cacheKey, cacheContext); this.timeoutStamp = timeoutStamp; } public CacheEntity(String cacheKey, Object cacheContext, int validityTime){ this(cacheKey, cacheContext); this.validityTime = validityTime; this.timeoutStamp = System.currentTimeMillis() + validityTime * 1000; } public String getCacheKey() { return cacheKey; } public void setCacheKey(String cacheKey) { this.cacheKey = cacheKey; } public Object getCacheContext() { return cacheContext; } public void setCacheContext(Object cacheContext) { this.cacheContext = cacheContext; } public long getTimeoutStamp() { return timeoutStamp; } public void setTimeoutStamp(long timeoutStamp) { this.timeoutStamp = timeoutStamp; } public int getValidityTime() { return validityTime; } public void setValidityTime(int validityTime) { this.validityTime = validityTime; } }
List缓存处理对象:
package limeCache.self; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * @projName:WZServer * @className:CacheHandler * @description:缓存操作类,对缓存进行管理,采用处理队列,定时循环清理的方式 * @creater:Administrator * @creatTime:2013年7月22日 上午9:18:54 * @alter:Administrator * @alterTime:2013年7月22日 上午9:18:54 * @remark: * @version */ public class CacheListHandler { private static final long SECOND_TIME = 1000; private static final SimpleConcurrentMap<String, CacheEntity> map; private static final List<CacheEntity> tempList; static{ tempList = new ArrayList<CacheEntity>(); map = new SimpleConcurrentMap<String, CacheEntity>(new HashMap<String, CacheEntity>(1<<18)); new Thread(new TimeoutTimerThread()).start(); } /** * 增加缓存对象 * @param key * @param ce */ public static void addCache(String key, CacheEntity ce){ addCache(key, ce, ce.getValidityTime()); } /** * 增加缓存对象 * @param key * @param ce * @param validityTime 有效时间 */ public static synchronized void addCache(String key, CacheEntity ce, int validityTime){ ce.setTimeoutStamp(System.currentTimeMillis() + validityTime * SECOND_TIME); map.put(key, ce); //添加到过期处理队列 tempList.add(ce); } /** * 获取缓存对象 * @param key * @return */ public static synchronized CacheEntity getCache(String key){ return map.get(key); } /** * 检查是否含有制定key的缓冲 * @param key * @return */ public static synchronized boolean isConcurrent(String key){ return map.containsKey(key); } /** * 删除缓存 * @param key */ public static synchronized void removeCache(String key){ map.remove(key); } /** * 获取缓存大小 * @param key */ public static int getCacheSize(){ return map.size(); } /** * 清除全部缓存 */ public static synchronized void clearCache(){ tempList.clear(); map.clear(); System.out.println("clear cache"); } static class TimeoutTimerThread implements Runnable { public void run(){ while(true){ try { checkTime(); } catch (Exception e) { e.printStackTrace(); } } } /** * 过期缓存的具体处理方法 * @throws Exception */ private void checkTime() throws Exception{ //"开始处理过期 "; CacheEntity tce = null; long timoutTime = 1000L; //" 过期队列大小 : "+tempList.size()); if(1 > tempList.size()){ System.out.println("过期队列空,开始轮询"); timoutTime = 1000L; Thread.sleep(timoutTime); return; } tce = tempList.get(0); timoutTime = tce.getTimeoutStamp() - System.currentTimeMillis(); //" 过期时间 : "+timoutTime); if(0 < timoutTime){ //设定过期时间 Thread.sleep(timoutTime); return; } System.out.print(" 清除过期缓存 : "+tce.getCacheKey()); //清除过期缓存和删除对应的缓存队列 tempList.remove(tce); removeCache(tce.getCacheKey()); } } }
Timer方式
package limeCache.self; import java.util.HashMap; import java.util.Timer; import java.util.TimerTask; /** * @projName:WZServer * @className:CacheHandler * @description:缓存操作类,对缓存进行管理,清除方式采用Timer定时的方式 * @creater:Administrator * @creatTime:2013年7月22日 上午9:18:54 * @alter:Administrator * @alterTime:2013年7月22日 上午9:18:54 * @remark: * @version */ public class CacheTimerHandler { private static final long SECOND_TIME = 1000;//默认过期时间 20秒 private static final int DEFUALT_VALIDITY_TIME = 20;//默认过期时间 20秒 private static final Timer timer ; private static final SimpleConcurrentMap<String, CacheEntity> map; static{ timer = new Timer(); map = new SimpleConcurrentMap<String, CacheEntity>(new HashMap<String, CacheEntity>(1<<18)); } /** * 增加缓存对象 * @param key * @param ce */ public static void addCache(String key, CacheEntity ce){ addCache(key, ce, DEFUALT_VALIDITY_TIME); } /** * 增加缓存对象 * @param key * @param ce * @param validityTime 有效时间 */ public static synchronized void addCache(String key, CacheEntity ce, int validityTime){ map.put(key, ce); //添加过期定时 timer.schedule(new TimeoutTimerTask(key), validityTime * SECOND_TIME); } /** * 获取缓存对象 * @param key * @return */ public static synchronized CacheEntity getCache(String key){ return map.get(key); } /** * 检查是否含有制定key的缓冲 * @param key * @return */ public static synchronized boolean isConcurrent(String key){ return map.containsKey(key); } /** * 删除缓存 * @param key */ public static synchronized void removeCache(String key){ map.remove(key); } /** * 获取缓存大小 * @param key */ public static int getCacheSize(){ return map.size(); } /** * 清除全部缓存 */ public static synchronized void clearCache(){ if(null != timer){ timer.cancel(); } map.clear(); System.out.println("clear cache"); } /** * @projName:WZServer * @className:TimeoutTimerTask * @description:清除超时缓存定时服务类 * @creater:Administrator * @creatTime:2013年7月22日 上午9:34:39 * @alter:Administrator * @alterTime:2013年7月22日 上午9:34:39 * @remark: * @version */ static class TimeoutTimerTask extends TimerTask{ private String ceKey ; public TimeoutTimerTask(String key){ this.ceKey = key; } @Override public void run() { CacheTimerHandler.removeCache(ceKey); System.out.println("remove : "+ceKey); } } }
timer方式有点是适用性更强,因为每个缓存的过期时间都可以独立配置的;ist只能适用于缓存时间都一样的线性过期。从性能开销方面,因为timer是与缓存对象数量成正比的,在缓存量很大的时候,在缓存时间内系统开销也随之提高;而list方式只要一个线程管理过期清理就可以了。
-- -- --
对timer方式的改进,定时程序只需要一个就可以了,过期时间,通过一个对象保存,根据每个对象的过期时间判断是否移除该缓存。于是得到下面的版本:
package limeCache.self.strong; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; /** * 在缓存的时候,同时记录下该key,缓存时间,失效周期 * 在读取缓存的时候,更新该key的缓存时间, * 定时器每两个小时运行一次,检查每个key是否过期,如果过期,删除Jboss中的cache * */ public class CacheTimerMamager{ private static final long SECOND_TIME = 1000;//毫秒 private static final long DEFUALT_VALIDITY_TIME = SECOND_TIME * 60 * 60 * 2;//默认过期时间 :2小时 private static final Timer timer ; private static final Map<String, CacheOutTime> map; static{ timer = new Timer(); map = new HashMap<String, CacheOutTime>(); timer.schedule(new CacheTimerTask(), DEFUALT_VALIDITY_TIME, DEFUALT_VALIDITY_TIME); } /** * 增加缓存对象 * @param key * @param ce */ public static synchronized void addCache(String key){ CacheOutTime cot = map.get(key); long outTime = System.currentTimeMillis()+DEFUALT_VALIDITY_TIME; if(cot==null){ cot = new CacheOutTime(key, outTime); map.put(key, cot); }else{ //更新该key的过期时间 cot.setTimeoutStamp(outTime); } } //移除cache /** * 考虑,在多线程时,当有线程已经取得缓存对象时,删掉了缓存,会产生什么情况 */ public static synchronized void removeCache() { CacheOutTime cot; long currentTime = System.currentTimeMillis(); for (String key : map.keySet()) { cot = map.get(key); if(cot.getTimeoutStamp()<=currentTime){ System.out.println("remove : "+key); } } } static class CacheTimerTask extends TimerTask{ @Override public void run() { //移除cache CacheTimerMamager.removeCache(); } } } class CacheOutTime { private String cacheKey; private long timeoutStamp;//过期时间戳,在最后一次访问该key的时候计算得到 public CacheOutTime() { super(); } public CacheOutTime(String cacheKey, long timeoutStamp) { super(); this.cacheKey = cacheKey; this.timeoutStamp = timeoutStamp; } public String getCacheKey() { return cacheKey; } public void setCacheKey(String cacheKey) { this.cacheKey = cacheKey; } public long getTimeoutStamp() { return timeoutStamp; } public void setTimeoutStamp(long timeoutStamp) { this.timeoutStamp = timeoutStamp; } }
啦啦啦
啦啦啦