β

LocalCache

项籍 106 阅读

引言

一般来说,应用中的数据主要来自于数据库和缓存,现有的缓存中间件已经能满足大多数场景,但是有些场景更适合做服务器本地缓存,这就需要自己去实现了,这里给出了方攀的一个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));
                                                                        }

                                                                    });
    }
作者:项籍
项籍20130121的博客
原文地址:LocalCache, 感谢原作者分享。

发表评论