1,前言
该缓存为Java并发编程实战中实现的,主要是为了将性能瓶颈并转变为可伸缩瓶颈,用于提升单线程的性能;该缓存主要是为了解决高开销高耗时接口的性能提升。而基于此接口,能够尝试实现接口的幂等性。
2,代码
1,缓存的实现
class Memorizer<A,V> implements Computable<A,V> {
private final Map<A, FutureTask<V>> cache = new ConcurrentHashMap<>();
//通过组合实现接口的调用
private Computable<A,V> origin;
public Memorizer(Computable<A,V> origin) {
this.origin = origin;
}
@Override
public V compute(A arg) throws InterruptedException {
//循环为了实现当接口调用失败或调用中断时,重新开启接口的调用
while (true) {
FutureTask<V> f = cache.get(arg);
if (f == null) {
//开启异步来处理复杂接口的调用
Callable<V> task = () -> origin.compute(arg);
FutureTask<V> futureTask = new FutureTask<>(task);
//put-if-absent 缺少即加入
f = cache.putIfAbsent(arg, futureTask);
if (f == null) {
//first put
f = futureTask;
f.run();
}
}
try {
//此处会等待任务返回值
return f.get();
} catch (CancellationException e) {
//如果任务取消,则删除后重新开启任务
cache.remove(arg,f);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
2,测试
@FunctionalInterface
interface Computable<A,V> {
V compute(A arg) throws InterruptedException;
}
/**
* 高耗时接口DEMO
*/
class ExpensiveFunction implements Computable<String, Integer> {
@Override
public Integer compute(String arg) throws InterruptedException {
Thread.sleep(2000);
return new Random().nextInt(100);
}
}
/**
* 缓存的实现
* cache 装饰者模式实现
*/
class Memorizer<A,V> implements Computable<A,V> {
private final Map<A, FutureTask<V>> cache = new ConcurrentHashMap<>();
//通过组合实现接口的调用
private Computable<A,V> origin;
public Memorizer(Computable<A,V> origin) {
this.origin = origin;
}
@Override
public V compute(A arg) throws InterruptedException {
//循环为了实现当接口调用失败或调用中断时,重新开启接口的调用
while (true) {
FutureTask<V> f = cache.get(arg);
if (f == null) {
//开启异步来处理复杂接口的调用
Callable<V> task = () -> origin.compute(arg);
FutureTask<V> futureTask = new FutureTask<>(task);
//put-if-absent 缺少即加入
f = cache.putIfAbsent(arg, futureTask);
if (f == null) {
//first put
f = futureTask;
f.run();
}
}
try {
//此处会等待任务返回值
return f.get();
} catch (CancellationException e) {
//如果任务取消,则删除后重新开启任务
cache.remove(arg,f);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
// 测试接口调用
public class SimpleCache {
public static void main(String[] args) throws InterruptedException {
Computable<String, Integer> ex = new ExpensiveFunction();
Computable<String, Integer> m = new Memorizer<>(ex);
long s = System.currentTimeMillis();
Integer compute = m.compute("1");
long e = System.currentTimeMillis();
System.err.println(compute+": "+(e-s));
long s1 = System.currentTimeMillis();
Integer compute1 = m.compute("1");
long e1 = System.currentTimeMillis();
System.err.println(compute1+": "+(e1-s1));
}
}
3,原理
1,通过`ConcurrentHashMap`确保程序的线程安全,使用原子方法`putIfAbsent`,避免`先检查后执行`的操作漏洞(该漏洞可能会造成两次或多次接口调用)
2,使用`FutureTask`提交缓存的高效性,同时也能避免`先检查后执行`的漏洞,如果使用原返回值,那么编码的实现可能如下,这种情况必然会产生资源浪费的情况
...
V result = cache.get(key);
if (result == null) {
//此处必然会存在多次接口调用的问题,会造成资源的浪费
result = 原接口.compute(arg);
//显然,此处使用putIfAbsent的结果也是相同的
catch.put(arg,result);
}
...
4,幂等性尝试
1,幂等性场景
- 网络延迟导致多次重复提交。
- 表单重复提交。
2,实现策略
调用接口时,保证能有唯一编码确定接口调用标识,比如保存订单时订单的ID。将订单作为key值储存缓存并实现异步的接口调用,最终future.get()来等待线程回归。