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