多线程缓存

多线程缓存

缓存作为当前服务端最常见的技术,若是在单线程前提下,缓存只需要一个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等待一个的执行结果,并发性能优秀!

总结

对于并发问题的处理,并不是线程安全就行,还需要考虑性能,两者都很重要,并发本身就是为了提高性能,不能盲目加锁,在进行加锁的时候应该考虑锁的粒度,以及考虑用什么锁。


多线程缓存
http://example.com/2023/11/02/多线程缓存/
作者
ykexc
发布于
2023年11月2日
许可协议