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     }

在该版本中应该完美了解决了原先提出的问题。但是还是存在如下问题:没有解决缓存预期的问题,没有解决缓存清理问题。

posted @ 2017-05-09 14:45  wsMrLin  阅读(1472)  评论(2编辑  收藏  举报