重试依然有问题

来源:13-8 污染问题的解决

灵森

2020-04-04

public class MayFail implements Computable<String, Integer>{
@Override
public Integer compute(String arg) throws Exception {
double random = Math.random();
if (random > 0.5) {
throw new IOException(Thread.currentThread().getName()+" 读取文件出错");
}
Thread.sleep(2000);
System.out.println(arg+“被加入缓存”);
return Integer.valueOf(arg);
}
}


public class ImoocCache9<A, V> implements Computable<A, V> {

private final ConcurrentHashMap<A, Future<V>> cache 
= new ConcurrentHashMap<>();

private final Computable<A, V> c;

public ImoocCache9(Computable<A, V> c) {
    this.c = c;
}

@Override
public V compute(A arg) throws InterruptedException, ExecutionException {
    while (true) {
        Future<V> f = cache.get(arg);
        if (f == null) {
            FutureTask<V> ft = new FutureTask<>(()->c.compute(arg));
            f = cache.putIfAbsent(arg, ft);
            if (f == null) {
                f = ft;
                System.out.println(Thread.currentThread().getName()+" 从FutureTask调用了计算函数");
                ft.run();
            }
        }
        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) {
            System.out.println(Thread.currentThread().getName()+" "+e.getMessage()+" 计算错误,需要重试");
            cache.remove(arg);
        }
    }
}

public static void main(String[] args) throws Exception {
    ImoocCache9<String, Integer> expensiveComputer = new ImoocCache9<>(
            new MayFail());
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Integer result = expensiveComputer.compute("666");
                System.out.println("第一次的计算结果:" + result);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Integer result = expensiveComputer.compute("666");
                System.out.println("第二次的计算结果:" + result);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Integer result = expensiveComputer.compute("667");
                System.out.println("第三次的计算结果:" + result);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
}

}

打印:

Thread-2 从FutureTask调用了计算函数
Thread-1 从FutureTask调用了计算函数
Thread-2 java.io.IOException: Thread-2 读取文件出错 计算错误,需要重试
Thread-1 java.io.IOException: Thread-1 读取文件出错 计算错误,需要重试
Thread-0 java.io.IOException: Thread-1 读取文件出错 计算错误,需要重试
Thread-1 从FutureTask调用了计算函数
Thread-2 从FutureTask调用了计算函数
Thread-1 java.io.IOException: Thread-1 读取文件出错 计算错误,需要重试
Thread-0 从FutureTask调用了计算函数
Thread-1 从FutureTask调用了计算函数
Thread-2 java.io.IOException: Thread-2 读取文件出错 计算错误,需要重试
Thread-1 java.io.IOException: Thread-1 读取文件出错 计算错误,需要重试
Thread-0 java.io.IOException: Thread-0 读取文件出错 计算错误,需要重试
Thread-1 从FutureTask调用了计算函数
Thread-2 从FutureTask调用了计算函数
Thread-1 java.io.IOException: Thread-1 读取文件出错 计算错误,需要重试
Thread-0 从FutureTask调用了计算函数
Thread-1 从FutureTask调用了计算函数
Thread-2 java.io.IOException: Thread-2 读取文件出错 计算错误,需要重试
Thread-1 java.io.IOException: Thread-1 读取文件出错 计算错误,需要重试
Thread-0 java.io.IOException: Thread-0 读取文件出错 计算错误,需要重试
Thread-1 从FutureTask调用了计算函数
Thread-2 从FutureTask调用了计算函数
Thread-1 java.io.IOException: Thread-1 读取文件出错 计算错误,需要重试
Thread-0 从FutureTask调用了计算函数
Thread-1 从FutureTask调用了计算函数
Thread-0 java.io.IOException: Thread-0 读取文件出错 计算错误,需要重试
Thread-0 从FutureTask调用了计算函数

//重试导致缓存重复添加****************************************

667被加入缓存
666被加入缓存
666被加入缓存
第一次的计算结果:666
第三次的计算结果:667
第二次的计算结果:666

Process finished with exit code 0

老师,看打印结果,重试有时候会导致缓存重复添加,您的代码也是,运行多次也会出现这个问题,没想明白咋回事?

写回答

1回答

悟空

2020-04-06

这是因为,发生异常时,会有remove的操作:当前一个线程putIfAbsent放入值后,后一个线程此时remove,然后去putIfAbsent时会发现拿到的future是null,所以又会计算一次。

一种改进思路:执行remove时,如果发现之前已经有其他线程remove过,则此时不再remove。

1
5
灵森
回复
悟空
Okay
2020-04-08
共5条回复

深度解密Java并发工具,精通JUC,成为并发多面手

JUC全方位讲解,构建并发工具类知识体系

1599 学习 · 573 问题

查看课程