引言
一般来说,应用中的数据主要来自于数据库和缓存,现有的缓存中间件已经能满足大多数场景,但是有些场景更适合做服务器本地缓存,这就需要自己去实现了,这里给出了方攀的一个LocalCache的例子,并已在应用中得到了很好的应用。
关键词介绍
1、SoftReference
只要有足够的内存,就一直保持对象,直到发现内存吃紧且没有Strong Ref时才回收对象,结合超时时间可以用来实现缓存。
2、ReferenceQueue
使得对象即使被GC了也可以持有ref,结合HashMap可以缩小键-值映射关系的数目。
实现原理
1、service类
public class CacheManager { public interface ValueGenerator { Object generate(); } private final CacheUtil cache; private final Lock lock = new ReentrantLock(); public CacheManager(int cacheSize, int expriedTime){ cache = new CacheUtil(true, cacheSize, expriedTime); } /** * <pre> * 返回 key 对应的 value,如果 value 已经超时, * 则使用 ValueGenerator 产生新的 value,并返回。 * 使用此方法,可以避免多线程同时产生新 value 的问题。 * </pre> * * @param key * @param generator * @return */ public Object get(String key, ValueGenerator generator) { Object result = cache.get(key); if (result != null) { return result; } try { lock.lock(); result = cache.get(key); if (result != null) { return result; } Object value = generator.generate(); cache.put(key, value); return value; } finally { lock.unlock(); } } }
2、cacheUtil
public class CacheUtil { private final static float LOAD_FACTOR = 0.75f; private final Map<Object, CacheEntry> cacheMap; private int maxSize; private long lifetime; private final ReferenceQueue<Object> queue; private final Lock lock = new ReentrantLock(); public CacheUtil(boolean soft, int maxSize){ this(soft, maxSize, 0); } public CacheUtil(boolean soft, int maxSize, int lifetime){ this.maxSize = maxSize; this.lifetime = lifetime * 1000; this.queue = soft ? new ReferenceQueue<Object>() : null; int buckets = (int) (maxSize / LOAD_FACTOR) + 1; cacheMap = new HashMap<Object, CacheEntry>(buckets, LOAD_FACTOR); } private void emptyQueue() { if (queue == null) { return; } while (true) { CacheEntry entry = (CacheEntry) queue.poll(); if (entry == null) { break; } Object key = entry.getKey(); if (key == null) { continue; } CacheEntry currentEntry = cacheMap.remove(key); if ((currentEntry != null) && (entry != currentEntry)) { cacheMap.put(key, currentEntry); } } } private void expungeExpiredEntries() { emptyQueue(); if (lifetime == 0) { return; } int cnt = 0; long time = System.currentTimeMillis(); for (Iterator<CacheEntry> t = cacheMap.values().iterator(); t.hasNext();) { CacheEntry entry = t.next(); if (entry.isValid(time) == false) { t.remove(); cnt++; } } } public int size() { try { lock.lock(); expungeExpiredEntries(); return cacheMap.size(); } finally { lock.unlock(); } } public void clear() { try { lock.lock(); if (queue != null) { for (CacheEntry entry : cacheMap.values()) { entry.invalidate(); } while (queue.poll() != null) { } } cacheMap.clear(); } finally { lock.unlock(); } } public void put(Object key, Object value) { try { lock.lock(); emptyQueue(); long expirationTime = (lifetime == 0) ? 0 : System.currentTimeMillis() + lifetime; CacheEntry newEntry = newEntry(key, value, expirationTime, queue); CacheEntry oldEntry = cacheMap.put(key, newEntry); if (oldEntry != null) { oldEntry.invalidate(); return; } if (maxSize > 0 && cacheMap.size() > maxSize) { expungeExpiredEntries(); if (cacheMap.size() > maxSize) { Iterator<CacheEntry> t = cacheMap.values().iterator(); CacheEntry lruEntry = t.next(); t.remove(); lruEntry.invalidate(); } } } finally { lock.unlock(); } } public Object get(Object key) { try { lock.lock(); emptyQueue(); CacheEntry entry = cacheMap.get(key); if (entry == null) { return null; } long time = (lifetime == 0) ? 0 : System.currentTimeMillis(); if (entry.isValid(time) == false) { cacheMap.remove(key); return null; } return entry.getValue(); } finally { lock.unlock(); } } public void remove(Object key) { try { lock.lock(); emptyQueue(); CacheEntry entry = cacheMap.remove(key); if (entry != null) { entry.invalidate(); } } finally { lock.unlock(); } } public void setCapacity(int size) { try { lock.lock(); expungeExpiredEntries(); if (size > 0 && cacheMap.size() > size) { Iterator<CacheEntry> t = cacheMap.values().iterator(); for (int i = cacheMap.size() - size; i > 0; i--) { CacheEntry lruEntry = t.next(); t.remove(); lruEntry.invalidate(); } } maxSize = size > 0 ? size : 0; } finally { lock.unlock(); } } public void setTimeout(int timeout) { try { lock.lock(); emptyQueue(); lifetime = timeout > 0 ? timeout * 1000L : 0L; } finally { lock.unlock(); } } protected CacheEntry newEntry(Object key, Object value, long expirationTime, ReferenceQueue<Object> queue) { if (queue != null) { return new SoftCacheEntry(key, value, expirationTime, queue); } else { return new HardCacheEntry(key, value, expirationTime); } }
3、cacheEntry(cacheUtil的内部类)
private static interface CacheEntry { boolean isValid(long currentTime); void invalidate(); Object getKey(); Object getValue(); }
private static class SoftCacheEntry extends SoftReference<Object> implements CacheEntry { private Object key; private long expirationTime; SoftCacheEntry(Object key, Object value, long expirationTime, ReferenceQueue<Object> queue){ super(value, queue); this.key = key; this.expirationTime = expirationTime; } public Object getKey() { return key; } public Object getValue() { return get(); } public boolean isValid(long currentTime) { boolean valid = (currentTime <= expirationTime) && (get() != null); if (valid == false) { invalidate(); } return valid; } public void invalidate() { clear(); key = null; expirationTime = -1; } }
应用接入
/** 初始化内存cache大小为256,过期时间为300即300秒 */ private static final CacheManager cacheManager = new CacheManager(256, 300);
public Map<Integer, DisplayArea<Course>> getCourseDisplayArea(final boolean withDetail, final int floorId) { /* 注意key唯一性,需要加上withDetail */ return (Map<Integer, DisplayArea<Course>>) cacheManager.get(COURSE_CACHE_KEY_BY_FLOOR + floorId + withDetail, new ValueGenerator() { @Override public Object generate() { List<Integer> areaIds = DisplayAreaFeature.getCourseDisplayAreas(floorId); Map<Integer, DisplayArea<Course>> displayAreas = getCourseDisplayArea(withDetail, areaIds); // 解析扩展属性 并填充标签 return fillTags(displayAreas, parseExtProperty(displayAreas)); } }); }