多线程缓存事例

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

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

				 }
			 }
		 }
	 }
时间: 2024-08-03 22:58:52

多线程缓存事例的相关文章

多线程之事例

1.[JAVA多线程]如何解决一个生产者与消费者问题 如何解决一个生产者与消费者问题生产者与消费者问题是多线程同步的一个经典问题.生产者和消费者同时使用一块缓冲区,生产者生产商品放入缓冲区,消费者从缓冲区中取出商品.我们需要保证的是,当缓冲区满时,生产者不可生产商品:当缓冲区为空时,消费者不可取出商品. 下面介绍java中几种解决同步问题的方式 (1)wait()与notify()方法 (2)Lock与Condition机制 (3)BlockingQueue阻塞队列 [1]wait()与noti

多线程+缓存计算

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86

Android 框架修炼-自己封装双缓存管理框架库

一.概述 Android开发中,网络请求是很重要的一部分,而缓存网络请求来的图片或者响应结果字符串或者结果流,既可以省流量,同时也可以帮助我们 解决无网或弱网情况下加载情况,当然也可以提升程序性能效率.纵所周知,缓存管理中肯定需要用到内存缓存,这里我们采用LruCache来管理内存的缓存. LruCahce虽然速度快,但是只是内存级别的缓存,为了实现持久化的缓存,我们还需要文件级别的缓存,也就是说我们要把缓存保存到文件,而文件则是保存 到手机存储或者SD卡存储中,即实现Disk级别的缓存,这里我

多线程学习之多线程访问共同资源(队列,多线程,锁机制)实例

模拟场景:main方法为网络请求线程(也叫生产者线程),在网络请求线程中开启四个线程(消费者线程),进行高效处理队列中的共同资源(生产者线程生产的共同资源),等待资源处理完毕,网络请求线程执行结束,响应客户端. 消费者线程体 1 /** 2 * 3 */ 4 package com.benxq.Queue; 5 6 import java.text.SimpleDateFormat; 7 import java.util.Date; 8 import java.util.concurrent.B

学习笔记 --- 缓存、动态页面静态化、网站优化

http://www.cnblogs.com/cs_net/archive/2011/03/17/1986620.html 一.缓存 缓存(Cache)技术在软件开发过程中有着广泛的用途, 它对提升软件性能和改善客户体验有很大帮助. 所谓缓存, 是指将那些经常重复的操作结果暂时存放起来, 在以后的执行过程中, 只要使用前面的暂存结果即可. 缓存技术在日常生活中随处可见, 就拿排队买票来说吧: 买票时需要先排队, 等轮到自己了, 再告诉售票员你需要买那里的票, 售票员查询完后告诉你有还是没有. 若

Android-Universal-Image-Loader三大组件DisplayImageOptions、ImageLoader、ImageLoaderConfiguration详解

一.介绍 Android-Universal-Image-Loader是一个开源的UI组件程序,该项目的目的是提供一个可重复使用的仪器为异步图像加载,缓存和显示.所以,如果你的程序里需要这个功能的话,那么不妨试试它.因为已经封装好了一些类和方法.我们 可以直接拿来用了.而不用重复去写了.其实,写一个这方面的程序还是比较麻烦的,要考虑多线程缓存,内存溢出等很多方面. 二.具体使用 一个好的类库的重要特征就是可配置性强.我们先简单使用Android-Universal-Image-Loader,一般

Android图片加载库的理解

前言 这是“基础自测”系列的第三篇文章,以Android开发需要熟悉的20个技术点为切入点,本篇重点讲讲Android中的ImageLoader这个库的一些理解,在Android上最让人头疼是从网络中获取图片,显示,回收,任何一个环节有问题都可能直接OOM,当需要加载大量的图片的时候,每当快速滑,有时候会很卡,甚至会因为内存溢出而崩溃.这里讲解的库是:Universal_Image_Loader. 内容目录 ImageLoader设计原理 ImageLoader流程图 ImageLoader的使

android LRUCache解析

LRU(Least Recently Used)最近最少使用算法 原理 缓存保存了一个强引用(Android 2.3开始,垃圾回收器更倾向于回收弱引用和软引用,软引用和弱引用变得不可靠,Android 3.0中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放)限制值的数量. 每当值被访问的时候,它会被移动到队列的头部. 当缓存已满的时候加入新的值时,队列中最后的值会出队,可能被回收 LRUCache内部维护主要是通过LinkedHashMap实现 这是一个安全的线程,多线程

Java并发包中Lock的实现原理

Lock 的简介及使用 Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制.本质上Lock仅仅是一个接口(位于源码包中的java\util\concurrent\locks中),它包含以下方法 //尝试获取锁,获取成功则返回,否则阻塞当前线程 void lock(); //尝试获取锁,线程在成功获取锁之前被中断,则放弃获取锁,抛出异常 void lockInterruptibly() throws InterruptedException; //尝试获取锁,获取锁成