在这篇博客中,主要把之前看的书的内容记录一下,个人感觉还是可以的,原题是这样的:开发一个高效的缓存。这里指的是单机.
首先我来看当前的一个版本
1 public interface Computable{2 R compute(T input) throwsInterruptedException;3 }
1 public class Memoizer1 implements Computable{2
3 private final Map cache =new HashMap<>();4
5 private final Computablecomputable;6
7 public Memoizer1(Computablecomputable) {8 this.computable =computable;9 }10
11 public synchronized R compute(T input) throwsInterruptedException {12 R result=cache.get(input);13 if(result ==null){14 result =computable.compute(input);15 cache.put(input,result);16 }17 returnresult;18 }19 }
在该版本中利用HashMap来保存之前计算的结果,compute方法首先检查缓存中是否有结果,没有则计算,把其结果放入缓存并且返回。大家都知道HashMap不是线程安全的,因此要确保多个线程同时访问的时,Memoizer1采用把对整个方法compute进行同步,这样的结果导致调用该方法被串行化,如果compute的执行时间比较长,那么后面的线程需要等待更长的时间,结果可能比不用缓存更加糟糕。
接着我们对该版本进行进一步的优化,把HashMap改为ConcurrentHashMap,因为ConcurrentHashMao是线程安全的,因此在访问底层的Map的时候不需要进行同步。因此避免了在对compute方法进行同步带来的串行性。
1 public class Memoizer2 implements Computable{2
3 private final Map cache = new ConcurrentHashMap<>();4
5 private final Computablecomputable;6
7 public Memoizer2(Computablecomputable) {8 this.computable =computable;9 }10
11 public R compute(T input) throwsInterruptedException {12 R result=cache.get(input);13 if(result ==null){14 result =computable.compute(input);15 cache.put(input,result);16 }17 returnresult;18 }19 }
在该版本也存在一些不足,当两个线程同时调用compute时存在一个漏洞,可能会导致计算相同的值。缓存的目的是避免相同的数据被多次计算。我们知道FutureTask 表示一个计算过程,这个过程可能已经完成,也可能正在进行。如果FutureTask结果可用,调用FutureTask.get()将立即得到结果,否则它会一直阻塞,知道计算结果出来再返回。
1 public class Memoizer3 implements Computable{2
3 private final Map cache =new ConcurrentHashMap<>();4
5 private final Computablecomputable;6
7 public Memoizer3(Computablecomputable) {8 this.computable =computable;9 }10
11 public R compute(final T input) throwsInterruptedException {12 Future future =cache.get(input);13 if (future==null){14 Callable callable = new Callable() {15 @Override16 public R call() throwsException {17 returncomputable.compute(input);18 }19 };20 FutureTask futureTask = newFutureTask(callable);21 future=futureTask;22 cache.put(input,future);23 futureTask.run();24 }25 try{26 returnfuture.get();27 } catch(ExecutionException e) {28 throw newRuntimeException(e);29 }30 }31 }
在第三个版本Memoizer3的实现几乎是完美的,它表现出非常好的并发性,如果结果已经计算出来则直接返回,如果其他线程正在计算该结果,那么新到的线程将一直等待这个结果被计算出来。它只有一个缺陷,即仍然存在两个线程计算出相同值的漏洞。
但是这个概率将远远小于第二个版本。由于compute方法中的if代码仍然是非原子的“先检查后执行”操作。因此两个线程仍然有可能在同一时间内调用compute来计算相同的值。在该版本中存在该问题的原因是,复合操作在底层Map对象上执行,而这个对象无法通过加锁来确保原子性。那么接着把Map.put()改为Map.putIfAbsent()即可。
1 public class Memoizer4 implements Computable{2
3 private final ConcurrentMap cache = new ConcurrentHashMap<>();4
5 private final Computablecomputable;6
7 public Memoizer4(Computablecomputable) {8 this.computable =computable;9 }10
11 public R compute(final T input) throwsInterruptedException {12 while (true){13 Future future =cache.get(input);14 if (future==null){15 Callable callable = new Callable() {16 @Override17 public R call() throwsInterruptedException {18 returncomputable.compute(input);19 }20 };21 FutureTask futureTask = newFutureTask(callable);22 future= cache.putIfAbsent(input, futureTask);//如果原先不存在,返回null
23 if(future==null){24 future=futureTask;25 futureTask.run();26 }27 }28 try{29 returnfuture.get();30 } catch(CancellationException e) {31 cache.remove(input,future);32 } catch(ExecutionException e) {33 throw newRuntimeException(e);34 }35 }36 }
在该版本中应该完美了解决了原先提出的问题。但是还是存在如下问题:没有解决缓存预期的问题,没有解决缓存清理问题。