高并发学习养成计划4

构建高效且可伸缩的结果缓存

简单的缓存可能将性能瓶颈转变为可伸缩性瓶颈,即使缓存适用于提升单线程的性能

 

使用HashMap和同步机制来缓存初始化数据

 

Memoizer1是使用synchronized和HashMap构建的缓存,虽然将计算结果是保存在了map中,但是由于将所有线程给串行化了,所以并发量极差,程序的伸缩性及其不友好。甚至可能在某些情况下还不如不加缓存的性能高。所以需要将该种实现方法进行改进。

 

使用ConcurrentHashMap实现缓存

 

通过Memoizer2可以分析出来,此种实现方式虽然改善了系统的伸缩性,但是在没有锁的情况下可能造成同一个算式多次计算(因为没有加锁,所以当线程1访问compute时,判断result为空,此时跳到线程2访问compute时,拿与线程1同一个数据,此时会判断为空,线程2又将进行计算)。这对于缓存来说完全是没有意义的。

 

 

 

 

利用FutureTask实现缓存

通过Memoizer3可以发现,该种实现的缓存已经近似完美。因为相比Memoizer1该种实现方式没有加锁,增加了系统的弹性。并且在存放前并没有进行复杂的运算,而是直接以一个假的结果放入cache中,这样在另一个线程过来的时候,获取result不为空,则不会重复进行运算,而是在get处等待 第一个运算的线程运算完成。与Memoizer2相比,大大减小了重复运算的概率。但是仔细研究下还是有问题的,还会在假的结果没有放进缓存的时候,可能这个时候另一个线程来了,那么将又要进行重复运算了

 

 

Mermoizer的最终实现

Mermoizer3的问题已经知道了,它的问题是出在若没有则添加不是原子操作,最后导致问题的出现。Mermoizer采用了concurrentHashMap中的同步方法错没有则添加,从而保证了这个过程的原子性。可能还有人会问为什么要加while循环。那是因为缓存的是future不是值,会导致缓存污染的问题(也就是当某个假值被存入缓存中,真实的值还在通过线程计算,那么当没有计算完这个结果而终止了这个线程,那么将在这个缓存中产生了垃圾值。那么以后取得值都是垃圾值,所以是及其不安全得,故我们要捕获CancellationException,当发现终止任务异常之后,应该立即将缓存中得假值清空)。这么清空得话,另一些在等待拿到值的线程,就不会拿到值。而我们要做的就是使这些在等待但没有拿到值得线程在循环一次操作,让新的线程去计算。当计算无误后,所有拿某个缓存值得线程都结束执行。

 

 

猜你喜欢

转载自blog.csdn.net/qq_37229498/article/details/85118445