打造高性能缓存(二)计算错误的异常处理
最后更新于:2022-08-16 10:21:38
计算过程并不是一帆风顺的,假设有一个计算类,他有一定的概率计算失败,该如何处理
有概率失败的实现类
public class MayFail implements Computable {
@Override
public Integer computer(String arg) throws Exception {
double random = Math.random();
if (random < 0.5) {
throw new IOException("读取文件失败");
}
Thread.sleep(3000);
return Integer.parseInt(arg);
}
}
测试调用
public class TestCache6 implements Computable {
private final Map> cache = new ConcurrentHashMap>();
private final Computable c;
// 具体的计算,在c的Computable 方法中完成
public TestCache6(Computable c) {
this.c = c;
}
@Override
public V computer(A arg) throws Exception {
Future f = cache.get(arg);
if (f == null) {
Callable callable = new Callable() {
@Override
public V call() throws Exception {
return c.computer(arg);
}
};
FutureTask ft = new FutureTask<>(callable);
// 修改put为putIfAbsent填补漏洞
f = cache.putIfAbsent(arg, ft);
if (f == null) {
f = ft;
System.out.println("从FutureTask调用了计算逻辑");
ft.run();
}
}
return f.get();
}
public static void main(String[] args) throws Exception {
TestCache6 expensiveComputer = new TestCache6<>(new MayFail());
new Thread(() -> {
try {
Integer result = expensiveComputer.computer("666");
System.out.println("第一次计算结果:" + result + ",时间:" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
Integer result = expensiveComputer.computer("666");
System.out.println("第二次计算结果:" + result + ",时间:" + System.currentTimeMillis());
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
结果
从FutureTask调用了计算逻辑
被取消了
java.util.concurrent.CancellationException
at java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.honor.concurrency.testCache.TestCache6.computer(TestCache6.java:45)
at com.honor.concurrency.testCache.TestCache6.lambda$main$0(TestCache6.java:67)
at java.lang.Thread.run(Thread.java:748)
java.util.concurrent.CancellationException
at java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.honor.concurrency.testCache.TestCache6.computer(TestCache6.java:45)
at com.honor.concurrency.testCache.TestCache6.lambda$main$0(TestCache6.java:67)
at java.lang.Thread.run(Thread.java:748)
可以看到线程在等待结果的时候,任务被取消了。抛出了CancellationException异常。
而当线程运行出现了计算错误的异常时,我们可以选择重试,可以选择使用while(true),这样是建立在我们知道这个时候出错,重试是最合适的处理方法
public V computer(A arg) throws InterruptedException, ExecutionException {
while (true) {
Future f = cache.get(arg);
if (f == null) {
Callable callable = new Callable() {
@Override
public V call() throws Exception {
return c.computer(arg);
}
};
FutureTask ft = new FutureTask<>(callable);
// 修改put为putIfAbsent填补漏洞
f = cache.putIfAbsent(arg, ft);
if (f == null) {
f = ft;
System.out.println("从FutureTask调用了计算逻辑");
ft.run();
}
}
try {
return f.get();
} catch (CancellationException e) {
// 被取消异常
System.out.println("被取消了");
e.printStackTrace();
throw e;
} catch (InterruptedException e) {
// 被中断异常
e.printStackTrace();
throw e;
} catch (ExecutionException e) {
// 运行时异常
System.out.println("计算错误,需要重试");
}
}
}
运行结果
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
计算错误,需要重试
发现一直都是运行错误,而我们的程序是当随机出的数字小于0.5才会抛出异常,即便运气再差,也不会一直都抛错,原因就是出现了缓存污染问题。
cache.putIfAbsent(arg, ft),当future为空的时候,才会往里面存放数据,并且不会重复的放,假设在第一次放到时候,就出错了,因为没有任何人来清理它,所以以后再来调用,拿到的结果都是不变的,都是之前发生错误的那个值
计算失败,移除fature,增加程序健壮性
修改程序try-catch的处理,只有出现了异常,就清理掉当前的future
try {
return f.get();
} catch (CancellationException e) {
// 被取消异常
System.out.println("被取消了");
cache.remove(arg);
throw e;
} catch (InterruptedException e) {
// 被中断异常
cache.remove(arg);
throw e;
} catch (ExecutionException e) {
// 运行时异常
cache.remove(arg);
System.out.println("计算错误,需要重试");
}
运行结果
从FutureTask调用了计算逻辑
计算错误,需要重试
从FutureTask调用了计算逻辑
第一次计算结果:666,时间:1660616474745