工作中经常会遇到这样的场景:一项复杂的工作耗费的时间比较长,为了避免重复这个过程,仅仅只需要一个线程去做这个工作就好了,其他的线程等待这个线程的工作结果,有几种方式可以实现这个模式,下面就总结了几种方式然后分析他们之间的不同。
1通过future实现
基本代码如下:
public class CacheTest<T> {
Map<Request, T> cache = new HashMap<Request, T>();
synchronized T requestInvoke(Request r){
T result = cache.get(r);
if( result != null){
return result;
}
result = doSomeThing();
cache.put(r, result);
return result;
}
T doSomeThing(){
。。。
}
}
需要注意的是需要重写Request 的hashCode和equals方法
采取了同步方法来避免多线程同时请求,但是问题对于整个方法的同步这个粒度较大,是灵活性不够,其他的线程必须等待,下面有一种不用同步方法的实现方式。
class CacheTest2<T> {
Map<Request, Future<T>> cache = new ConcurrentHashMap<Request, Future<T>>();
T requestInvoke(final Request r){
Future<T> f = cache.get(r);
if(f == null){
Callable<T> v = new Callable<T>(){
@Override
public T call() throws Exception {
return doSomeThing(r);
}
};
FutureTask<T> ft = new FutureTask<T>(v);
f = cache.putIfAbsent(r, ft);
if(f == null) {
f = ft;
ft.run();
}
}
return f.get();
}
T doSomeThing(Request r){
return null;
}
}
第一条线程首先在缓存中查找是否有相应的对象(futuredata),如果不存在的话那这个线程就是第一条线程,是要去生产realdata的,他会继续往下执行生产过程,
2对象锁
基本原理是每条线程来的时候检测缓存汇总有没有处理好的结果,或者是处理中的结果(使用临时对象,如果是临时对象,则线程会等待处理结果),第一条线程来将临时对象放进缓存,然后自己去生成真实的返回对象,整个工作完成后把真实对象放进缓存,然后唤醒其他的线程。
class CacheTest2{
//
Private static Object pendingGenerationMarker = new Object();
Map cache = new HashMap();
Object requestInvoke(Request r){
Object value;
synchronized (cache) {
do {
value = cache.get(r);
if (value == null) {
cache.put(r, pendingGenerationMarker);
break;
} else if (value == pendingGenerationMarker) {
try {
cache.wait();//此处的变种是可以设置等待时间
} catch (InterruptedException e) {
}
continue;
} else {
return value;
}
} while (true);
}
try {
value = doSomeThing(r);
} catch (Exception e) {
}finally{
synchronized (cache) {
cache.put(r, value);
cache.notifyAll();
}
}
return value;
}
Object doSomeThing(Request r){
…
}
}
这种方式比较灵活之处是在等待时间的处理方面,当等待时间超时后,逻辑处理可以返回null值,这样等待线程可以继续处理自己的其他逻辑,之后再回来查看是否复杂对象已经生产完毕。
对于缓存的结果数据如果较大,占据内存空间很多时,又不想太麻烦写一个缓存定时删除机制,可以使用weakReference和softReference来实现。WeakHashMap可以帮助实现这个功能。只需要将对象放入这个map,取出的时候首先取出Reference 实例,然后取出真实的值:(Reference) value).get()
在put的时候需要将值封装为WeakReference 的形式:
cache.put(key, new WeakReference(value));