打造高性能缓存(二)计算错误的异常处理

计算过程并不是一帆风顺的,假设有一个计算类,他有一定的概率计算失败,该如何处理

有概率失败的实现类

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才会抛出异常,即便运气再差,也不会一直都抛错,原因就是出现了缓存污染问题。

file

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

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注