java应用本地缓存

在java应用中,对于访问频率比较高,又不怎么变化的数据,常用的解决方案是把这些数据加入缓存。相比DB,缓存的读取效率快好不少。java应用缓存一般分两种,一是进程内缓存,就是使用java应用虚拟机内存的缓存;另一个是进程外缓存,现在我们常用的各种分布式缓存。相比较而言,进程内缓存比进程外缓存快很多,而且编码也简单;但是,进程内缓存的存储量有限,使用的是java应用虚拟机的内存,而且每个应用都要存储一份,有一定的资源浪费。进程外缓存相比进程内缓存,会慢些,但是,存储空间可以横向扩展,不受限制。

这里是几中场景的访问时间

-------------------------------------------------------------------

|         从数据库中读取一条数据(有索引)        |  十几毫秒  |

|         从远程分布式缓存读取一条数据              |  0.5毫秒    |

|         从内存中读取1MB数据                         |  十几微妙  |

-------------------------------------------------------------------

进程内缓存和进程外缓存,各有优缺点,针对不同场景,可以分别采用不同的缓存方案。对于数据量不大的,我们可以采用进程内缓存。或者只要内存足够富裕,都可以采用,但是不要盲目以为自己富裕,不然可能会导致系统内存不够。

下面要分享的是一个代码级别的,对进程内缓存的经验总结。面向jdk1.8版本。

在有效时间内缓存单个对象

public class LiveCache<T> {
    // 缓存时间
    private final int cacheMillis;
    // 缓存对象
    private final T element;
    // 缓存对象创建时间
    private final long createTime;

    public LiveCache(int cacheMillis, T element) {
        this.cacheMillis = cacheMillis;
        this.element = element;
        this.createTime = System.currentTimeMillis();
    }

    // 获取缓存对象
    public T getElement() {
        long currentTime = System.currentTimeMillis();
        if(cacheMillis > 0 && currentTime - createTime > cacheMillis) {
            return null;
        } else {
            return element;
        }
    }

    // 获取缓存对象,忽略缓存时间有效性
    public T getElementIfNecessary() {
        return element;
    }
}

public static void main(String[] args) {
    int cacheMilis = 1000 ;
    LiveCache<Object> liveCache = new LiveCache<>(cacheMilis, new Object()) ;

    liveCache.getElement() ;
    liveCache.getElementIfNecessary() ;

}

有效时间内,缓存单个对象,可异步刷新

@FunctionalInterface
public interface LiveFetch<T> {
    // 刷新缓存接口
    T fetch() ;
}

public class LiveManager<T> {
    // 缓存时间
    private int cacheMillis;
    // 缓存对象
    private LiveCache<T> liveCache;
    // 刷新缓存的对象
    private LiveFetch<T> liveFetch ;

    private Logger logger = LoggerFactory.getLogger(LiveManager.class) ;

    // 刷新缓存开关
    private boolean refresh = false ;

    public LiveManager(int cacheMillis, LiveFetch<T> liveFetch) {
        this.cacheMillis = cacheMillis ;
        this.liveFetch = liveFetch ;
    }

    /**
     * fetch cache ; if cache expired , synchronous fetch
     * @return
     */
    public T getCache() {

        initLiveCache();

        if(liveCache != null) {
            T t  ;
            if((t= liveCache.getElement()) != null) {
                return t ;
            } else {
                t = liveFetch.fetch() ;
                if(t != null) {
                    liveCache = new LiveCache<T>(cacheMillis, t) ;
                    return t ;
                }
            }
        }

        return null ;
    }

    /**
     * fetch cache ; if cache expired , return old cache and asynchronous fetch
     * @return
     */
    public T getCacheIfNecessary() {

        initLiveCache();

        if(liveCache != null) {
            T t  ;
            if((t= liveCache.getElement()) != null) {
                return t ;
            } else {
                refreshCache() ;
                return liveCache.getElementIfNecessary() ;
            }
        }

        return null ;
    }

    /**
     * init liveCache
     */
    private void initLiveCache() {
        if(liveCache == null) {
            T t = liveFetch.fetch() ;
            if(t != null) {
                liveCache = new LiveCache<T>(cacheMillis, t) ;
            }
        }
    }

    /**
     * asynchronous refresh cache
     */
    private void refreshCache() {

        if(refresh)
            return ;
        refresh = true ;
        try {
            Thread thread = new Thread(() -> {
                try {
                    T t = liveFetch.fetch();
                    if (t != null) {
                        liveCache = new LiveCache<>(cacheMillis, t);
                    }
                } catch (Exception e){
                    logger.error("LiveManager.refreshCache thread error.", e);
                } finally {
                    refresh = false ;
                }
            }) ;
            thread.start();
        } catch (Exception e) {
            logger.error("LiveManager.refreshCache error.", e);
        }
    }
}

public class Test {

    public static void main(String[] args) {
        int cacheMilis = 1000 ;
        LiveManager<Object> liveManager = new LiveManager<>(cacheMilis,() -> new Test().t1()) ;

        liveManager.getCache() ;
        liveManager.getCacheIfNecessary() ;
    }

    public Object t1(){

        return new Object() ;
    }
}

有效缓存内,缓存多个对象,map结构存储,可异步刷新

@FunctionalInterface
public interface LiveMapFetch<T> {
    // 异步刷新数据
    T fetch(String key) ;
}

public class LiveMapManager<T> {

    private int cacheMillis;
    private Map<String,LiveCache<T>> liveCacheMap;
    private LiveMapFetch<T> liveMapFetch;

    private Logger logger = LoggerFactory.getLogger(LiveMapManager.class) ;

    private boolean refresh = false ;

    public LiveMapManager(int cacheMillis, LiveMapFetch<T> liveMapFetch) {
        this.cacheMillis = cacheMillis ;
        this.liveMapFetch = liveMapFetch ;
    }

    /**
     * fetch cache ; if cache expired , synchronous fetch
     * @return
     */
    public T getCache(String key) {

        initLiveCache();

        T t ;
        if(liveCacheMap.containsKey(key) && (t = liveCacheMap.get(key).getElement()) != null) {
            return t ;
        } else {
            t = liveMapFetch.fetch(key) ;
            if(t != null) {
                LiveCache<T> liveAccess = new LiveCache<T>(cacheMillis, t) ;
                liveCacheMap.put(key, liveAccess) ;
                return t ;
            }
        }

        return null ;
    }

    /**
     * fetch cache ; if cache expired , return old cache and asynchronous fetch
     * @return
     */
    public T getCacheIfNecessary(String key) {

        initLiveCache();

        T t ;
        if(liveCacheMap.containsKey(key) && (t = liveCacheMap.get(key).getElement()) != null) {
            return t ;
        } else {
            if(liveCacheMap.containsKey(key)) {
                refreshCache(key) ;
                return liveCacheMap.get(key).getElementIfNecessary() ;
            } else {
                t = liveMapFetch.fetch(key) ;
                if(t != null) {
                    LiveCache<T> liveAccess = new LiveCache<T>(cacheMillis, t) ;
                    liveCacheMap.put(key, liveAccess) ;
                    return t ;
                }
            }
        }
        return t ;
    }

    /**
     * init liveCache
     */
    private void initLiveCache() {
        if(liveCacheMap == null) {
            liveCacheMap = new HashMap<>() ;
        }
    }

    /**
     * asynchronous refresh cache
     */
    private void refreshCache(String key) {

        if(refresh)
            return ;
        refresh = true ;
        try {
            Thread thread = new Thread(() -> {
                try {
                    T t = liveMapFetch.fetch(key);
                    if (t != null) {
                        LiveCache<T> liveAccess = new LiveCache<>(cacheMillis, t);
                        liveCacheMap.put(key, liveAccess);
                    }
                } catch (Exception e) {
                    logger.error("LiveMapManager.refreshCache thread error.key:",e);
                } finally {
                    refresh = false ;
                }
            }) ;
            thread.start();
        } catch (Exception e) {
            logger.error("LiveMapManager.refreshCache error.key:" + key, e);
        }
    }

}

public class Test {

    public static void main(String[] args) {
        int cacheMilis = 1000 ;
        LiveMapManager<Object> liveManager = new LiveMapManager<>(cacheMilis,(String key) -> new Test().t1(key)) ;

        liveManager.getCache("key") ;
        liveManager.getCacheIfNecessary("key") ;
    }

    public Object t1(String key){

        return new Object() ;
    }
}

resource : https://github.com/jxlzl1988/MyCache

时间: 2024-08-07 16:45:51

java应用本地缓存的相关文章

关于java中的本地缓存-总结概述

java中的本地缓存,工作后陆续用到,一直想写,一直无从下手,最近又涉及到这方面的问题了,梳理了一下.自己构造单例.guava.ehcache基本上涵盖了目前的大多数行为了.   为什么要有本地缓存? 在 系统中,有些数据,数据量小,但是访问十分频繁(例如国家标准行政区域数据),针对这种场景,需要将数据搞到应用的本地缓存中,以提升系统的访问效率,减 少无谓的数据库访问(数据库访问占用数据库连接,同时网络消耗比较大),但是有一点需要注意,就是缓存的占用空间以及缓存的失效策略. 为什么是本地缓存,而

Java学习之ConcurrentHashMap实现一个本地缓存

ConcurrentHashMap融合了Hashtable和HashMap二者的优势. Hashtable是做了线程同步,HashMap未考虑同步.所以HashMap在单线程下效率较高,Hashtable在多线程下同步操作能保证程序的正确性.  但是Hashtable每次执行同步操作都需要锁住整个结构. ConcurrentHashMap的出现就是为了解决Hashtable同步lock整个数据结构的问题.ConcurrentHashMap锁的方式是细颗粒度. ConcurrentHashMap将

java应用中的本地缓存

java中的本地缓存,工作后陆续用到,一直想写,一直无从下手,最近又涉及到这方面的问题了,梳理了一下.自己构造单例.guava.ehcache基本上涵盖了目前的大多数行为了. 为什么要有本地缓存?在 系统中,有些数据,数据量小,但是访问十分频繁(例如国家标准行政区域数据),针对这种场景,需要将数据搞到应用的本地缓存中,以提升系统的访问效率,减 少无谓的数据库访问(数据库访问占用数据库连接,同时网络消耗比较大),但是有一点需要注意,就是缓存的占用空间以及缓存的失效策略. 所谓的本地混存是相对于网络

Java本地缓存解决方案其一(使用Google的CacheBuilder)

前不久,业务实现上需要用到本地缓存来解决一些数据量相对较小但是频繁访问的数据,通过查找各种资料,找到了一种可以实现的方案--采用的是Google的CacheBuilder.下面是代码实现过程:1.首先在maven中引入下面的包: <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>19.0</versio

java本地缓存和redis缓存

  本地缓存 本地缓存存储在内存当中,实现缓存如下 首先需要引入包 <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.1</version> </dependency> 缓存服务接口: package com.mobcb.platform.service.common; im

Android -- ImageLoader本地缓存

传送门 <Android -- ImageLoader简析>  http://www.cnblogs.com/yydcdut/p/4008097.html 本地缓存 在缓存文件时对文件名称的修改提供了两种方式,每一种方式对应了一个Java类 1)  HashCodeFileNameGenerator,该类负责获取文件名称的hashcode然后转换成字符串. 2)  Md5FileNameGenerator,该类把源文件的名称同过md5加密后保存. 两个类都继承了FileNameGenerato

安卓网络请求图片到图片的三级缓存技术(内存缓存,本地缓存,网络缓存)

安卓网络请求图片,对于我们来说并不陌生,因为每个应用都有可能会用到这一技术.通常情况下,我们第一次都是从网络上请求图片资源,然后将 图片资源保存到内存和本地,下一次动态显示图片的时候就不需要再从网络上请求图片资源了,直接从本地或者内存中获取就可以了.这就涉及到图片 的三级缓存技术,分别是内存缓存,本地缓存,网络缓存. 缓存的流程图: 首先我们定义一个类叫ClassLoader: package com.jsako.showprodinfodemo; import java.io.FileOutp

故障排除 Mybatis ORA-01000 和 本地缓存问题

※异常信息 环境 MyBatis Oracle11.2c Terasoluna BatchCaused by: org.springframework.jdbc.UncategorizedSQLException: ### Error updating database. Cause: java.sql.SQLException: ORA-00604: 再帰SQLレベル1でエラーが発生しました.ORA-01000: 最大オープン?カーソル数を超えました.ORA-00604: 再帰SQLレベル1で

Android ImageLoader 本地缓存

本地缓存 在缓存文件时对文件名称的修改提供了两种方式,每一种方式对应了一个Java类 1)  HashCodeFileNameGenerator,该类负责获取文件名称的hashcode然后转换成字符串. 2)  Md5FileNameGenerator,该类把源文件的名称同过md5加密后保存. 两个类都继承了FileNameGenerator接口 在DefaultConfigurationFactory类中提供了一个工厂方法createFileNameGenerator,该方法返回了一个默认的F