目录
1. 缓存策略
正确使用缓存,可以避免让我们重复的去计算大量数据或者反复的去读取硬件设备。
缓存节约了时间,提高了吞吐量,但是消耗了内存
if value in cache
return value from cache
else
compute value
set value to cache
return value
2. 简单版缓存 ---- HashMap
/**
* 描述: 最简单的缓存形式: HashMap
*/
public class ImoocCache1 {
private final HashMap<String, Integer> cache = new HashMap<>();
public Integer compute(String userId) throws InterruptedException {
Integer result = cache.get(userId);
// 先检查HashMap里面有没有保存过之前的计算结果
if(result == null) {
// 如果缓存中找不到,那么需要现在计算一下结果,并且保存到HashMap中
result = doCompute(userId);
cache.put(userId, result);
}
return result;
}
private Integer doCompute(String userId) throws InterruptedException {
// 这里假设计算时间为5秒,所以以休眠时间代替计算
TimeUnit.SECONDS.sleep(5);
return new Integer(userId);
}
public static void main(String[] args) throws InterruptedException {
ImoocCache1 imoocCache1 = new ImoocCache1();
System.out.println("开始计算了");
Integer result = imoocCache1.compute("13");
System.out.println("第一次计算结果:" + result);
result = imoocCache1.compute("13");
System.out.println("第二次计算结果:" + result);
}
}
2.1 存在的问题
HashMap
不是线程安全的,线程不安全的缓存可能在多线程的情况下导致某一个数据被计算两次,违背了缓存的初衷- 设计不良好,缓存类和计算过程耦合性太高,缓存类既要负责缓存,又要负责计算新的数值,不利于扩展
2.2 为什么给HashMap加关键字final?
- 属性被声明为final后,该变量则只能被赋值一次,且一旦被赋值,final的变量就不能再改变
- 类中Map不需要改变,加上
final
关键字,增强了安全性
2.3 解决问题方法
2.3.1 方法一
synchronized
- 对
compute
方法加入synchronized
关键字
仍然存在以下问题
- 性能问题: 多个线程同时到
compute
的时候,由于HashMap是线程不安全的,所以如果多个线程同时put
、get
,会带来线程安全问题,所以这里用synchronized
来保证每个时刻最多只有一个线程能访问,但是显而易见这带来了性能问题。当多个线程同时想计算的时候,需要慢慢等待,严重时,性能甚至比不用缓存更差 - 代码复用能力差: 代码的复用能力很差,如果第二个类需要用缓存,难道要重新加一个HashMap,然后再加上compute方法吗?这样对代码的侵入性太高了,而且一旦我们的
compute
逻辑有变动,就要在之前使用了缓存的所有类中都一个个做出修改,违反了开闭原则,不可取
2.3.2 方法二
装饰者模式 + synchronized (刚好还没学装饰者模式,借此机会学了一下,先看原理,代码后面附上)
-
原有类和装饰器类必须继承同一个父类
再来看看一个煎饼果子类的UML图(辅助理解,后续写一下装饰者模式文章)
回到本篇文章,引入装饰者模式改进代码 -
Computable接口类(计算函数)
/** * 描述: 有一个计算函数compute,用来代表耗时计算,每个计算器都要实现这个接口,这样 * 就可以无侵入实现缓存功能 */ public interface Computable<A, V> { V compute(A arg) throws Exception; }
-
ExpensiveFunction(耗时实现类,实现Computable接口)
/** * 描述: 耗时计算的实现类,实现了Computable接口,但是本身不具备缓存能力, * 不需要考虑缓存的事情 */ public class ExpensiveFunction implements Computable<String, Integer> { @Override public Integer compute(String arg) throws Exception { Thread.sleep(5000); return Integer.valueOf(arg); } }
-
缓存类
/** * 描述: 用装饰者模式,给计算器自动添加缓存功能 */ public class ImoocCache3<A,V> implements Computable<A, V> { private final Map<A, V> cache = new HashMap(); // 计算函数接口 private final Computable<A, V> c; // 构造函数,传入接口的实现类 public ImoocCache3(Computable<A, V> c) { this.c = c; } // 对计算方法进行加锁,保证安全性 @Override public synchronized V compute(A arg) throws Exception { System.out.println("进入缓存机制"); V result = cache.get(arg); if(result == null) { result = c.compute(arg); cache.put(arg, result); } return result; } public static void main(String[] args) throws Exception { ImoocCache3<String, Integer> expensiveComputer = new ImoocCache3<>(new ExpensiveFunction()); new Thread(() -> { try { Integer result = expensiveComputer.compute("666"); System.out.println("第一次计算结果:" + result); } catch (Exception e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { Integer result = expensiveComputer.compute("667"); System.out.println("第二次计算结果:" + result); } catch (Exception e) { e.printStackTrace(); } }).start(); new Thread(() -> { try { Integer result = expensiveComputer.compute("666"); System.out.println("第三次计算结果:" + result); } catch