多线程缓存事例

注:文章示例由上而下,安全级别越高。

示例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) {
					 
				 }
			 }
		 }
	 }

 

posted @ 2017-01-09 18:56  孟凡柱的专栏  阅读(342)  评论(0编辑  收藏  举报