Java并发编程中高效缓存设计的哲学
文章目录
- 引言
- 详解缓存的设计和演进
- 基于缓存存储运算结果
- 锁分段散列减小锁粒度
- 异步化提升处理效率
- 原子化避免重复运算
- 小结
- 参考
引言
本文将基于并发编程和算法中经典的哈希取模、锁分段、 异步化、原子化。这几个核心设计理念编写逐步推演出一个相对高效的缓存工具,希望对你有所启发。
我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis。
为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。
详解缓存的设计和演进
基于缓存存储运算结果
我们有一批数据需要通过运算才能获得结果,而每一次运算大约耗时是500ms
,所以为了避免重复运算导致的等待,我们希望对应数据第一次运算的结果直接缓存到容器中,后续线程可直接通过容器获得结果:
于是我们就有了第一个版本,利用缓存避免非必要的重复计算,从而提升程序在单位时间内的吞吐量
public class ComputeCache {public final Map<Integer, Integer> cache = new HashMap<>();public synchronized int compute(int arg) {if (cache.containsKey(arg)) {//若存在直接返回结果return cache.get(arg);} else {//若不存在则计算后缓存并返回int result = doCompute(arg);cache.put(arg, result);return result;}}//模拟耗时的计算private int doCompute(int key) {ThreadUtil.sleep(500);return key << 1;}public synchronized int size() {return cache.size();}}
我们利用下面这段单元测试来验证缓存的性能和正确性,这里笔者也简单介绍一下几个比较核心的点:
- 声明本机CPU核心数+1的线程数执行并发运算
- 利用倒计时门闩控制线程并发流程起止,保证准确感知所有运算任务结束后,执行耗时统计
- 利用容器中最直观且容易检查出错误的属性size进行比对判断我们的缓存是否正确
最终在笔者的机器下5000
并发的耗时大约是26765ms
,整体还是不太符合我们的预期:
//初始化缓存工具<