现象
当缓存中没有数据的时候,需要到数据库中查询,俗称缓存穿透。
在高并发情况下,多个线程同时穿透去查询同一条数据的时候,对数据库压力很大,事实上也造成了多余无效的数据库查询。
分析
那么希望一种方法,可以只让一个线程去数据库查询,其他线程等待这个线程的查询结果,这样就可以极大的减轻数据库压力。
解决
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;
public class ConcurrentDbQueryService {
private static final int THREAD_POOL_SIZE = 20;
private final String threadPrefix = "conc-db-query-";
private final AtomicLong threadCount = new AtomicLong();
private final ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_POOL_SIZE, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, threadPrefix + threadCount.incrementAndGet());
}
});
private final Map<String, FutureTask<String>> runningTaskMap = new ConcurrentHashMap<>();
public String getByKey(String key) {
FutureTask<String> runningTask = runningTaskMap.get(key);
if (runningTask != null) {
try {
// 发现同一个 key 有其他线程在查询,直接等待它的结果
return runningTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
FutureTask<String> task = new FutureTask<>(() -> {
try {
// TODO 从 db 中获取数据
return null;
} finally {
// 任务完成后,把自己从 runningTaskMap 中拿掉
runningTaskMap.remove(key);
}
});
runningTask = runningTaskMap.putIfAbsent(key, task);
if (runningTask == null) {
// 说明没有其他线程做相同操作
runningTask = task;
threadPool.submit(runningTask);
}
try {
return runningTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
|
评论