首页 > 其他 > 详细

ConcurrentHashMap+FutureTask实现高效缓存

时间:2021-01-31 17:37:38      阅读:45      评论:0      收藏:0      [点我收藏+]

假设服务器有一个computer复杂运算,耗时较久,如果服务器要实现缓存,一种思路就是通过HashMap或者redis来做缓存

下面是使用HashMap的简单示例,源码取自《并发编程实战》

 1 package net.jcip.examples;
 2 
 3 import java.math.BigInteger;
 4 import java.util.*;
 5 
 6 import net.jcip.annotations.*;
 7 
 8 /**
 9  * Memoizer1
10  *
11  * Initial cache attempt using HashMap and synchronization
12  *
13  * @author Brian Goetz and Tim Peierls
14  */
15 public class Memoizer1 <A, V> implements Computable<A, V> {
16     @GuardedBy("this") private final Map<A, V> cache = new HashMap<A, V>();
17     private final Computable<A, V> c;
18 
19     public Memoizer1(Computable<A, V> c) {
20         this.c = c;
21     }
22 
23     public synchronized V compute(A arg) throws InterruptedException {
24         V result = cache.get(arg);
25         if (result == null) {
26             result = c.compute(arg);
27             cache.put(arg, result);
28         }
29         return result;
30     }
31 }
32 
33 
34 interface Computable <A, V> {
35     V compute(A arg) throws InterruptedException;
36 }
37 
38 class ExpensiveFunction
39         implements Computable<String, BigInteger> {
40     public BigInteger compute(String arg) {
41         // after deep thought...
42         return new BigInteger(arg);
43     }
44 }

我们看compute方法的逻辑,先从hashMap中取,取不到再计算后存入hashMap。这是最容易想到的逻辑,但由于hashMap不是线程安全的,所以做一下优化,用ConcurrentHashMap

 1 package net.jcip.examples;
 2 
 3 import java.util.*;
 4 import java.util.concurrent.*;
 5 
 6 /**
 7  * Memoizer2
 8  * <p/>
 9  * Replacing HashMap with ConcurrentHashMap
10  *
11  * @author Brian Goetz and Tim Peierls
12  */
13 public class Memoizer2 <A, V> implements Computable<A, V> {
14     private final Map<A, V> cache = new ConcurrentHashMap<A, V>();
15     private final Computable<A, V> c;
16 
17     public Memoizer2(Computable<A, V> c) {
18         this.c = c;
19     }
20 
21     public V compute(A arg) throws InterruptedException {
22         V result = cache.get(arg);
23         if (result == null) {
24             result = c.compute(arg);
25             cache.put(arg, result);
26         }
27         return result;
28     }
29 }

使用ConcurrentHashMap仍然存在一个问题,前面说过,compute的计算耗时较久,可能存在线程A已经快计算完了,但还没来得及放到ConcurrentHashMap中,线程B获取不到就自己开始计算了

我们理想的情况是,线程B发现ConcurrentHashMap中没有的时候,先去瞅一眼有没有其他线程已经开始计算了,有的话就等,没有的话再自己算,算完放到缓存中。

这时候可以想到用FutureTask, 将Future作为ConcurrentHashMap的Value,如果有结果可用,Future.get将立即返回结果,否则线程就会阻塞直到计算完成,下面用ConcurrentHashMap+FutureTask进行优化。

 1 package net.jcip.examples;
 2 
 3 import java.util.*;
 4 import java.util.concurrent.*;
 5 
 6 /**
 7  * Memoizer3
 8  * <p/>
 9  * Memoizing wrapper using FutureTask
10  *
11  * @author Brian Goetz and Tim Peierls
12  */
13 public class Memoizer3 <A, V> implements Computable<A, V> {
14     private final Map<A, Future<V>> cache
15             = new ConcurrentHashMap<A, Future<V>>();
16     private final Computable<A, V> c;
17 
18     public Memoizer3(Computable<A, V> c) {
19         this.c = c;
20     }
21 
22     public V compute(final A arg) throws InterruptedException {
23         Future<V> f = cache.get(arg);
24         if (f == null) {
25             Callable<V> eval = new Callable<V>() {
26                 public V call() throws InterruptedException {
27                     return c.compute(arg);
28                 }
29             };
30             FutureTask<V> ft = new FutureTask<V>(eval);
31             f = ft;
32             cache.put(arg, ft);
33             ft.run(); // call to c.compute happens here
34         }
35         try {
36             return f.get();
37         } catch (ExecutionException e) {
38             throw LaunderThrowable.launderThrowable(e.getCause());
39         }
40     }
41 }

上面的代码看似完美了,但还是存在一个明显的问题,就是if条件的“”先检查再执行”,两个线程仍有可能在同一时间进行了if判断,然后分别计算,分别放到缓存。所以并不能解决上面的问题

继续优化。使用ConcurrentHashMap.putIfAbsent来避免这个漏洞

 1 package net.jcip.examples;
 2 
 3 import java.util.concurrent.*;
 4 
 5 /**
 6  * Memoizer
 7  * <p/>
 8  * Final implementation of Memoizer
 9  *
10  * @author Brian Goetz and Tim Peierls
11  */
12 public class Memoizer <A, V> implements Computable<A, V> {
13     private final ConcurrentMap<A, Future<V>> cache
14             = new ConcurrentHashMap<A, Future<V>>();
15     private final Computable<A, V> c;
16 
17     public Memoizer(Computable<A, V> c) {
18         this.c = c;
19     }
20 
21     public V compute(final A arg) throws InterruptedException {
22         while (true) {
23             Future<V> f = cache.get(arg);
24             if (f == null) {
25                 Callable<V> eval = new Callable<V>() {
26                     public V call() throws InterruptedException {
27                         return c.compute(arg);
28                     }
29                 };
30                 FutureTask<V> ft = new FutureTask<V>(eval);
31                 f = cache.putIfAbsent(arg, ft);
32                 if (f == null) {
33                     f = ft;
34                     ft.run();
35                 }
36             }
37             try {
38                 return f.get();
39             } catch (CancellationException e) {
40                 cache.remove(arg, f);
41             } catch (ExecutionException e) {
42                 throw LaunderThrowable.launderThrowable(e.getCause());
43             }
44         }
45     }
46 }

完美实现。

但这里需要注意如果f.get()抛出异常时,需要将Future从缓存中移除,即

 cache.remove(arg, f);

不然可能存在缓存污染问题。

 

ConcurrentHashMap+FutureTask实现高效缓存

原文:https://www.cnblogs.com/sulishihupan/p/14352734.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!