Java记录并发峰值_Java并发,看到了,就记录下呗

这篇博客探讨了如何在Java中开发一个高效的缓存,从使用非线程安全的HashMap到线程安全的ConcurrentHashMap,并通过FutureTask解决并发计算问题。文章详细介绍了每个版本的优化思路,最终实现了并发计算的线程安全和避免重复计算。
摘要由CSDN通过智能技术生成

在这篇博客中,主要把之前看的书的内容记录一下,个人感觉还是可以的,原题是这样的:开发一个高效的缓存。这里指的是单机.

首先我来看当前的一个版本

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 }

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值