实现模式:
使用ConcurentHashMap+FutureTask实现缓存
灵感来源:
Java并发编程实战书籍.
适用场景:
大量重复请求且耗时计算处理皆可使用。
话不多说,上代码:
1定义任务处理接口,定义耗时计算方法
package com.hm.cache;
public interface Computer<A,V> {
V compute ( A arg) throws InterruptedException;
}
2编写自定义缓存处理类
import java.util.concurrent.*;
/**
* 使用ConcurentHashMap+FutureTask实现缓存
* A.输入
* V.输出
*/
public class HmCache<A, V> implements Computer<A, V> {
private final ConcurrentHashMap<A, Future<V>> hmcache = new ConcurrentHashMap<>();
private final Computer c;
public HmCache(Computer<A, V> computer) {
this.c = computer;
}
@Override
public V compute(final A arg) throws InterruptedException {
Future<V> f = hmcache.get(arg);
while (true) {
if (f == null) {
Callable<V> eval = new Callable<V>() {
@Override
public V call() throws Exception {
return (V) c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<>(eval);
Future<V> vFuture = hmcache.putIfAbsent(arg, ft);
//如果putIfAbsent返回为null.说明缓存hmcache中.当前真的不存在这个key值的缓存.
if (vFuture == null) {
// System.out.println("调用putIfAbsent返回空,当前参数"+arg+ " 第一次访问缓存!执行耗时方法!");
f = ft;
ft.run();//调用ft.run.会执行call()方法.call会执行.testCache的taskCompute对象的computer.执行耗时方法.
}
} else {
// System.out.println("命中缓存!参数" + arg + " 当前线程"+Thread.currentThread().getName());
}
try {
return f.get(1000l, TimeUnit.MILLISECONDS);//耗时方法执行结束 or 获取到缓存 则在这个地方会进行返回.超过5秒报错.
} catch (Exception e) {
System.out.println("报错.移除缓存 参数"+arg+" 当前线程"+Thread.currentThread().getName()+" 开始重试.");
hmcache.remove(arg);
}
}
}
}
这边有几个设计亮点:
a.避免了显示使用锁来确保数据一致性问题。转而使用ConcurrentHashMap的putIfAbsent方法 ,在hmchche.get()之后进行二次校验,控制了if的非原子"先检查在操作"的问题。
b.不是等返回结果之后,在进行缓存,而是缓存之后,异步执行,利用FutureTask的get进行等待返回。控制了线程并发时候的同步等待以及重复计算问题。
3测试代码
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class testCache {
private static final Computer<String, Integer> taskCompute = new Computer<String, Integer>() {
Random random = new Random();
@Override
public Integer compute(String arg) throws InterruptedException {
int i = random.nextInt(5000);
Thread.sleep(i);//随机模拟耗时计算.最大睡眠5秒.f.get()超过2秒报错.报错之后执行那个任务的线程会自动重试.因为while(true)机制.
return Integer.parseInt(arg);
}
};
private static final Computer<String, Integer> HMCACHE = new HmCache<>(taskCompute);
public static Integer calculate(int factor) throws InterruptedException {
return HMCACHE.compute(factor + "");
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
Random random = new Random();
for (int i = 0; i < 100; i++) {
final int factor = random.nextInt(10);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Integer calculate = testCache.calculate(factor);
System.out.println("当前线程"+Thread.currentThread().getName()+" 获取到值:"+calculate);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executorService.submit(runnable);
}
}
}
到此构建完毕.