#线程内缓存
线程内缓存的概述就不说了,可以查看:java缓存概述
本篇文章主要说说常见的线程内缓存,这里分为两类:自定义线程内缓存、轻量级线程内缓存框架。
##自定义线程缓存
利用java的基本数据结构List、Map、Set等,自定义一个缓存对应。
自定义线程缓存示例:
import java.util.HashMap;
public class MyCache {
//测试代码
public static void main(String[] args) {
CustomCache cache = CustomCache.instance();
cache.put("2313", 13213);
cache.put("jkf1", 132);
System.out.println(cache.get("jkf"));
System.out.println(cache.get("jkf1"));
}
}
//自定义缓存示例,单利必须是要保证的
class CustomCache<V> {
private static CustomCache instance = new CustomCache();
private HashMap<String, V> cache = new HashMap<>();
private CustomCache(){}
public static CustomCache instance() {
return instance;
}
public void put(String key, V t) {
this.cache.put(key, t);
}
public V get(String key) {
return cache.get(key);
}
}
上述自定义示例,就是简单的包装一个HahMap,对频繁访问的数据进行缓存。自定义缓存存在如下的缺点:
- 多线程并发访问。
- 存储数据过多,存在内存溢出,即没有缓存淘汰策略。
- 一些缓存的统计数据无法提供。
当然上述缺陷,我们都可以在上述代码上,进行精细化开发,解决上述的缺点,但是这样的开发量比较大,与初衷背离:我只是想简简单单的使用一下,不想再开发一个专业的缓存。
轻量级线程内缓存框架
主要是说google的guava cache,这个轻量级的线程内缓存。
###使用guava cache
a. 引入方便,只需要引用google开源java类库即可,guava cache只是其中的一个包。
b. 使用简洁,Guava cache对泛型具有良好的支持,支持多种类型的缓存。例如:
String=Object,Integer=Object。
c. 不需要配置文件,直接通过代码配置缓存的各种参数,例如:并发线程量(concurrencyLevel,写的线程数)、容器初始容量(initialCapacity)、缓存移除通知(removalListener)、缓存不命中的加载数据方法(CacheLoader的load)、缓存失效策略(expireAfterWrite、maximumSize(LRU)),当然上述方法中“缓存不命中的加载数据方法”只有特殊的缓存实例支持。
d. 最特殊的:guava cache 是单线程,guava cache不会创建线程来维护缓存。
###使用示例
guava cache的缓存实例类型有多种,这里只是简单的介绍“可加载缓存(LoadingCache)”的使用,其他类型的缓存实例,请看下一篇专门的guava cache架构分析。
通过时间失效进行缓存淘汰
public static void main(String[] args) {
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
//缓存失效策略:写后30分钟
.expireAfterWrite(30L, TimeUnit.MINUTES)
//缓存未命中的策略:通过load方法查询获取数据
.build(new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) throws Exception {
return new Random().nextInt(10);
}
});
}
更完善的示例
public static void main(String[] args) {
LoadingCache<String, Integer> cache = CacheBuilder.newBuilder()
.concurrencyLevel(8)
.initialCapacity(10)
.maximumSize(100)
.recordStats()
.removalListener(new RemovalListener<String, Integer>() {
@Override
public void onRemoval(RemovalNotification<String, Integer> notification) {
}
}).build(new CacheLoader<String, Integer>() {
@Override
public Integer load(String key) throws Exception {
return new Random().nextInt(10);
}
});
}
###什么时候使用guava cache
使用场景
- 你愿意消耗一些内存空间来提升速度。
- 你预料到某些键会被查询一次以上。
- 缓存中存放的数据总量不会超出内存容量。(Guava Cache是单个应用运行时的本地缓存。它不能把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)
- guava cache可以缓存 少量的 频繁查询 的数据。
- guava cache的缓存失效,并不是立刻失效,而是延迟失效,原因:guava cache并不会启动线程,而是利用利用当前线程,进行缓存失效处理,例如会在写操作后调用缓存清理,如果写操作太少会在读操作进行缓存清理。当然如果对缓存清理要求严格,可以自己创建维护线程,进行缓存清理工作,例如:定时任务、其他线程
- 因为使用的是缓存,所以需要考虑缓存一致性对需求的要求。
- 因为是线程内缓存,guava cache没有提供缓存的持久化。
- Guava cache可以根据不同的业务设置不同的cache,可以做到同一业务的同一处理,但是又会引发缓存的离散,这个需要衡量。
guava cache的框架分析,见下一篇