多线程缓存事例
注:文章示例由上而下,安全级别越高。
示例1.
public interface Computable<A,V>{ V compute(A arg) throws InterruptedException; } public class ExpensiveFunction implements Computable<String,BigInteger>{ public BigInteger compute(String arg){ //在经过长时间的计算后 return new BigInteger(arg); } } public class Memoizer1<A,V> implements Computable<A, V>{ @GuardedBy("this") private final Map<A,V> cache = new HashMap<A, V>(); private final Computable<A, V> c; public Memoizer1(Computable<A, V> c){ this.c = c; } public synchronized V compute(A arg) throws InterruptedException{ V result = cache.get(arg); if(result ==null){ result = c.compute(arg); cache.put(arg, result); } return result; } }
问题是:HashMap 不是线程安全的,因此采用的是将compute方法进行同步。但是这样只能保证每次只有一个线程执行compute方法,有明显的可伸缩性问题。
示例2.
public class Memoizer2<A,V> implements Computable<A, V>{ private final Map<A,V> cache = new ConcurrentHashMap<A, V>();//线程安全,高效 private final Computable<A,V> c; private Memoizer2(Computable<A,V> c){ this.c = c; } public V compute(A arg) throws InterruptedException{ V result = cache.get(arg); if(result == null ){ result = c.compute(arg); cache.put(arg,result); } return result; } }
示例2问题在于:如果某个线程启动了一个开销很大的计算,而其他线程并不知道这个计算正在进行,那么很可能会重复这个计算。
示例3.
public class Memoizer3<A,V> implements Computable<A, V>{ private final Map<A,Future<V>> cache = new ConcurrentHashMap<A, Future<V>>(); private final Computable<A,V> c; private Memoizer3(Computable<A,V> c){ this.c = c; } public V compute(final A arg) throws InterruptedException{ Future<V> f = cache.get(arg);//检查计算是否存在 if(f == null){ Callable<V> eval = new Callable<V>(){ public V call() throws InterruptedException{ return c.compute(arg); } }; FutureTask<V> ft = new FutureTask<V>(eval);//不存在,创建FutureTask f = ft; cache.put(arg, ft);//注册到map中 ft.run();//开始执行计算 } try { return f.get(); //获得最后计算结果 } catch (ExecutionException e) { } } }
FutureTask :表示一个计算的过程,这个过程可能已经计算完成,也可能正在进行。如果有结果可用,那么FutureTask.get将立即返回结果,否则会一直阻塞,直到结果计算出来再将其返回。
示例3问题在于:仍然存在两个线程重复计算的问题。因为if语句块是复合操作(“若没有则添加”),无法保证原子性。解决这个问题也很简单,只要使用ConcurrentMap 中的原子方法 putIfAbsent就可以啦。
请看示例4
public class Memoizer4<A,V> implements Computable<A, V>{ private final Map<A,Future<V>> cache = new ConcurrentHashMap<A, Future<V>>(); private final Computable<A,V> c; private Memoizer4(Computable<A,V> c){ this.c = c; } public V compute(final A arg) throws InterruptedException{ while(true){ Future<V> f = cache.get(arg);//检查计算是否存在 if(f == null){ Callable<V> eval = new Callable<V>(){ public V call() throws InterruptedException{ return c.compute(arg); } }; FutureTask<V> ft = new FutureTask<V>(eval);//不存在,创建FutureTask f = ft; cache.putIfAbsent(arg, ft);//注册到map中, putIfAbsent原子方法 ft.run();//开始执行计算 } try { return f.get(); //获得最后计算结果 } catch (ExecutionException e) { } } } }