多线程缓存
缓存作为当前服务端最常见的技术,若是在单线程前提下,缓存只需要一个HashMap即可,但在多线程的情况下就会出现很多问题
实现Computable
1 2 3
| interface Computable<A, V> { V compute(A args); }
|
V1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Memo<A, V> implements Computable<A, V> { private final HashMap<A, V> hashMap; private final Computable<A, V> computable; public Memo(Computable<A, V> computable) { hashMap = new HashMap<>(); this.computable = computable; } @Override public V compute(A args) { if (!hashMap.containsKey(args)) { return hashMap.get(args); } V res = computable.compute(args); hashMap.put(args, res); return res; } }
|
不考虑并发场景,单线程下这样写足够。但在并发场景下,此代码会导致多次重复写。
V2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Memo<A, V> implements Computable<A, V> {
private final HashMap<A, V> hashMap;
private final Computable<A, V> computable; public Memo(Computable<A, V> computable) { hashMap = new HashMap<>(); this.computable = computable; }
@Override public synchronized V compute(A args) { if (!hashMap.containsKey(args)) { return hashMap.get(args); } V res = computable.compute(args); hashMap.put(args, res); return res; } }
|
完全解决了并发问题,非常安全,但是太慢了,每来一个都得等上一个执行完毕。
V3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Memo<A, V> implements Computable<A, V> {
private final ConcurrentHashMap<A, V> hashMap;
private final Computable<A, V> computable; public Memo(Computable<A, V> computable) { hashMap = new ConcurrentHashMap<>(); this.computable = computable; }
@Override public V compute(A args) { if (!hashMap.containsKey(args)) { return hashMap.get(args); } V res = computable.compute(args); hashMap.put(args, res); return res; } }
|
解决了可以多个同时compute
的问题,但又回到了最初有重复的问题(假设compute
时间特别长),有什么方式可以避免这些重复操作呢?如果能异步执行
就好了。
V3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class Memo<A, V> implements Computable<A, V> {
private final ConcurrentHashMap<A, Future<V>> hashMap;
private final Computable<A, V> computable;
public Memo(Computable<A, V> computable) { hashMap = new ConcurrentHashMap<>(); this.computable = computable; }
@Override public V compute(A args) { while (true) { Future<V> future = hashMap.get(args); if (future == null) { Callable<V> eval = () -> computable.compute(args); FutureTask<V> futureTask = new FutureTask<>(eval); future = hashMap.putIfAbsent(args, futureTask); if (future == null) { future = futureTask; futureTask.run(); } } try { return future.get(); } catch (CancellationException e) { hashMap.remove(args); } catch (ExecutionException | InterruptedException e) { throw new IllegalStateException(e.getCause()); } } } }
|
完美解决方案: 使用Future来存Value,异步执行Future,让重复的args等待一个的执行结果,并发性能优秀!
总结
对于并发问题的处理,并不是线程安全就行,还需要考虑性能,两者都很重要,并发本身就是为了提高性能,不能盲目加锁,在进行加锁的时候应该考虑锁的粒度,以及考虑用什么锁。