• 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     }

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

  • 相关阅读:
    CTF-Reverse-[GXYCTF2019]luck_guy
    凸度偏差与收益率曲线
    【翻译】理解 LSTM 网络
    基于 Keras 用 LSTM 网络做时间序列预测
    AIMR 固定收益推荐读物
    基于 Keras 用深度学习预测时间序列
    预测美国债券回报
    久期增加会提高长期预期回报吗?
    市场收益率预期与远期收益率
    sql server 查询时会锁表吗?
  • 原文地址:https://www.cnblogs.com/liferecord/p/6830531.html
Copyright © 2020-2023  润新知