Java缓存

常见的缓存算法有LRU、LFU和FIFO等算法。

1. LRU (Least Recently Used,最近最少使用)

算法根据数据的最近访问记录来淘汰数据,其原理是如果数据最近被访问过,将来被访问的概率比较高,最常见的实现是使用一个链表保存缓存数据,详细具体算法如下:

1)新数据插入到链表头部;

2)每当缓存数据命中,则将数据移到链表头部;

3)当链表满的时候,将链表尾部的数据丢弃;


2.LFU (Least Frequently Used, 最不经常使用)

算法根据数据的历史访问频率来淘汰数据,其原理是如果数据过去被访问次数越多,将来被访问的概率相对比较高。LFU的每个数据块都有一个引用计数,所有数据块按照引用计数排序,具有相同引用计数的数据块则按照时间排序。

具体算法如下:

1)新加入数据插入到队列尾部(因为引用);

2)队列中的数据被访问后,引用计数增加,队列重新排序;

3)当需要淘汰数据时,将已经排序的列表最后的数据块删除;


3.FIFO (First  In First Out,先进先出)

算法是根据先进先出原理来淘汰数据的,实现上是最简单的一种,具体算法如下:

1)新访问的数据插入FIFO队列尾部,数据在FIFO队列中顺序移动;

2)淘汰FIFO队列头部的数据;


评价一个缓存算法的好坏的标准主要有两个,一是命中率高,二是算法要容易实现。当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重。LFU效率要高于LRU,且能够避免周期性或者偶发性的操作导致缓存命中率下降的问题。但LRU需要记录数据的历史访问记录,一旦数据访问模式改变,LFU需要长时间来使用新的访问模式,即:LFU存在历史数据影响将来数据的“缓存污染”效用。FIFO虽然实现很简单,但是命中率很低,实际上也很少使用这种算法。


LRU实现:

import java.util.LinkedHashMap;
import java.util.Collection;
import java.util.Map;
import java.util.ArrayList;

/**
* An LRU cache, based on <code>LinkedHashMap</code>.
*
* <p>
* This cache has a fixed maximum number of elements (<code>cacheSize</code>).
* If the cache is full and another entry is added, the LRU (least recently used) entry is dropped.
*
* <p>
* This class is thread-safe. All methods of this class are synchronized.
*
* <p>
* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
* Multi-licensed: EPL / LGPL / GPL / AL / BSD.
*/
public class LRUCache<K,V> {

private static final float   hashTableLoadFactor = 0.75f;

private LinkedHashMap<K,V>   map;
private int                  cacheSize;

/**
* Creates a new LRU cache.
* @param cacheSize the maximum number of entries that will be kept in this cache.
*/
public LRUCache (int cacheSize) {
   this.cacheSize = cacheSize;
   int hashTableCapacity = (int)Math.ceil(cacheSize / hashTableLoadFactor) + 1;
   map = new LinkedHashMap<K,V>(hashTableCapacity, hashTableLoadFactor, true) {
      // (an anonymous inner class)
      private static final long serialVersionUID = 1;
      @Override protected boolean removeEldestEntry (Map.Entry<K,V> eldest) {
         return size() > LRUCache.this.cacheSize; }}; }

/**
* Retrieves an entry from the cache.<br>
* The retrieved entry becomes the MRU (most recently used) entry.
* @param key the key whose associated value is to be returned.
* @return    the value associated to this key, or null if no value with this key exists in the cache.
*/
public synchronized V get (K key) {
   return map.get(key); }

/**
* Adds an entry to this cache.
* The new entry becomes the MRU (most recently used) entry.
* If an entry with the specified key already exists in the cache, it is replaced by the new entry.
* If the cache is full, the LRU (least recently used) entry is removed from the cache.
* @param key    the key with which the specified value is to be associated.
* @param value  a value to be associated with the specified key.
*/
public synchronized void put (K key, V value) {
   map.put (key, value); }

/**
* Clears the cache.
*/
public synchronized void clear() {
   map.clear(); }

/**
* Returns the number of used entries in the cache.
* @return the number of entries currently in the cache.
*/
public synchronized int usedEntries() {
   return map.size(); }

/**
* Returns a <code>Collection</code> that contains a copy of all cache entries.
* @return a <code>Collection</code> with a copy of the cache content.
*/
public synchronized Collection<Map.Entry<K,V>> getAll() {
   return new ArrayList<Map.Entry<K,V>>(map.entrySet()); }

} // end class LRUCache

4 .缓存应用

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import net.blogjava.frankiegao123.log.slf4j.Log;
import net.blogjava.frankiegao123.log.slf4j.LogFactory;

/**
 * <p>System.Config 配置缓存</p>
 *
 * @author frankiegao123
 * 2010-6-10 下午02:48:35
 */
@Component("configCache")
public class ConfigCache implements ConfigService {

    private final static Log log = LogFactory.getLog(ConfigCache.class);

    /**
     * 更新缓存时记录的时间
     */
    private volatile long time = 0L;

    /**
     * 正在更新缓存时的门闩,为 true 时表示当前没有更新缓存,为 true 时表示当前正在更新缓存
     */
    private volatile boolean updateGate = true;

    /**
     * 缓存容器
     */
    private Map<String, SysConfig> cache = new ConcurrentHashMap<String, SysConfig>();

    private CommonDao commonDao;

    @Autowired
    public ConfigCache(CommonDao commonDao) {
        this.commonDao = commonDao;
        log.info("initializing cache...");
        refreshCache();
        time = System.currentTimeMillis();
        log.info("initialized cache finished, cache size: {}, set cache time to current: {}, cache timeout: {}ms", cache.size(), time, ConfigConstant.CACHE_TIMEOUT);
    }

    /**
     * <p>根据配置的键名获取配置值</p>
     *
     * @param configKey
     * @return
     * @author frankiegao123
     * 2010-6-10 上午11:18:33
     */
    public SysConfig getSysConfig(String configKey) {
        long current = System.currentTimeMillis();
        if(updateGate && isTimeout(current)) {
            synchronized (this) {
                if(updateGate) {
                    timeoutSynRefresh(current);
                }
            }
        }
        return cache.get(configKey);
    }

    /**
     * <p>超时时更新缓存。该方法需要在同步环境中调用</p>
     * @param current
     * @author frankiegao123
     * 2010-6-10 上午11:16:30
     */
    private void timeoutSynRefresh(long current) {
        updateGate = false;
        log.info("refresh cache start..., time out: {}, size: {}, set updateGate to false", (current - time) / 1000.0, cache.size());
        try {
            refreshCache();
            time = current;
            log.info("refresh cache finished, size after update: {}, set cache time to current: {}", cache.size(), String.valueOf(time));
        } catch (Exception e) {
            log.error("refresh cache failed", e);
        } finally {
            updateGate = true;
            log.info("refresh cache finished, set updateGate to true");
        }
    }

    /**
     * <p>更新缓存数据</p>
     *
     * @author frankiegao123
     * 2010-6-10 上午11:15:55
     */
    private void refreshCache() {
        List<SysConfig> configs = commonDao.getSysConfigs();
        for(Iterator<SysConfig> i = configs.iterator(); i.hasNext(); ) {
            SysConfig config = i.next();
            cache.put(config.getKey(), config);
        }
        commonDao.clear();
        SysConfig config = cache.get(SysConfig.TEST_KEY);
        if(config == null) {
            log.error("refresh cache, cannot find TEST_KEY");
        } else {
            log.info("refresh cache, find TEST_KEY = [{}]", config.getValue());
        }
    }

    /**
     * <p>缓存是否超时</p>
     *
     * @param current
     * @return
     * @author frankiegao123
     * 2010-6-10 上午11:16:12
     */
    private boolean isTimeout(long current) {
        return (current - time >= ConfigConstant.CACHE_TIMEOUT);
    }

    Collection<SysConfig> getSysConfigs() {
        return Collections.unmodifiableCollection(cache.values());
    }

    int getSize() {
        return cache.size();
    }

    long getTime() {
        return time;
    }

    boolean isUpdateGate() {
        return updateGate;
    }

    void refresh() {
        time = 0L;
        log.info("refresh: reset cache time to 0");
        getSysConfig("none");
        log.info("refresh: refresh cache finished, cache: {0}", String.valueOf(time));
    }
}

上面这代码的运行机制是这样的:
1:设定超时时间,当某一请求来到时缓存超过限定时间时,重新刷新缓存。没有请求来时,即便超时了也不会去主动刷新,待下一个请求来就会刷新了。
2:在刷新缓存的过程中,若有其他请求来时,这些请求继续使用旧的数据。(这一点之前考虑了很久,最后确定为这一方案,否则有一个在刷新缓存时会阻塞很多的线程。)


参考:

http://my.oschina.net/u/866190/blog/188712  非常详细

http://www.source-code.biz/snippets/java/6.htm  Java代码

http://www.oschina.net/code/snippet_55577_3887#5951


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值