缓存的简单实现方式
参考: 《Java 并发实践》 李大狗大神的大作和源码非常值得分析阅读
1.String
的hashCode
:数据缓存在类变量中
字符串类维护了一个常量池—每当使用String str="xxx"
创建对象,都会首先检查字符串是否在常量池中—有的话直接返回池中对象的实例引用,否则则创建一个对象返回并将对象放进池中。
没当调用 String.hashCode()
方法时,字符串的hashCode
便会缓存在String
类变量中,下次查询是直接返回。
2.数据缓存在XXHashMap
中
HashMap
和ConcurrentHashMap
是最简单的缓存实现,必须使用缓存的项目初期可用XXHashMap
实现。注意点有:
- 数据量不可太大,因为百万级别数据的扩容会耗费很长时间,可能造成后续数据积压。参考;
- 由
key
获取value
消耗时间太长的话,<K,V>
中V
可 存放FutrueTask<Value>
; - k->v 的计算过程如果被取消或者中断,value存放的是无效的value, 此时需要移除无效的value,常用代码如下:
...
try{
return feature.get();
}catch(Exception e){
cache.remove(key);
}
...
综上,稍合理的缓存实现如下:
/**
*计算接口
*/
public interface Computable<K,V>{
V compute(A arg) throws InterruptedException;
}
/**
*带有缓存实现的计算接口
*/
public class CachedComputer<K,V> implements Computable<K,V>{
private final ComcurrentMap<K,Future<V>> cache = new ConcurrentHashMap<A,Future<V>>();
private final Computable<K,V> c;
public CachedComputer(Computable<K,V> c){
this.c=c;
}
@Override
public V compute(final K key) throws InterruptedException{
while(true){
Future<V> f=cache.get(key);
if(f==null){
Callable<V> eval=new Callable<V>(){
@Override
public V call() throws InterruptedException{
return c.compute(key);
}
};
FutureTask<V> ft=new FutureTask<V>(eval);
f=cache.putIfAbsent(key,ft);
if(f==null){
f=ft;
f.run();
}
}
try{
return f.get();
}Cache(Exception e){
cache.remove(key);
}
}
}
}
3. 第二节的补充
第二节的实现仍然有几个问题亟ji2待解决:
- 缓存逾期问题—旧值逾期报废以为新值腾出空间;
- 缓存大小问题。
解决方法如下:
//逾期报废
value 包装成带有时间戳的对象(k,v,time),当put()或者get()时更新时间为System.currentTime();
启动线程检查逾期的value并移除,并且每次执行都sleep()一定的时间。
//缓存大小
使用LRU策略。