`
airu
  • 浏览: 270802 次
  • 性别: Icon_minigender_1
  • 来自: 云南
社区版块
存档分类
最新评论

并发与缓存——读《JCP》

 
阅读更多
缓存方法在我们编程中经常遇到。例如一个通过很复杂计算的值,但是一旦计算以后,就不再变化,我们可以用缓存存放。最简单的写法如下:
Object value = null;
if ( (value = cache.get(key)) == null ) {
   value = compteValue(key);
  }
cache.put(key, value);


如果不考虑多线程,这看起来也没什么问题。实际上的应用,则不太可能这样写,因为
首先:缓存的出现,正是为了减少cpu的消耗,也就是说,compteValue这个方法应该是很耗时的(如果很简单就获取了,也没必要使用缓存)。其次,可能有多个线程并发访问这个缓存,也就是频繁使用,所以这里要说的也就是并发下的缓存。
《JCP》中给出的例子循序渐进,非常好。我们也可以看到,concurrent包中的一些类的使用。下面我把这些例子放在这里,以供学习。
public class Memoier1<K, V> implements Computable<K, V> {
	private final Map<K, V> cache = new HashMap<K, V>();

	private final Computable<K, V> c;

	public Memoier1(Computable<K, V> c) {
		this.c = c;
	}

	@Override
	public synchronized V compute(K key) throws InterruptedException {

		V result = cache.get(key);
		if (result == null) {
			result = c.compute(key);
			cache.put(key, result);
		}
		return result;

	}
}


很眼熟吧。虽然使用了 synchronized ,但是很不辛,这样的同步只会造成更低下的性能。因为synchronized 此时把整个方法锁住了。也就是每个调用线程只能排队调用吧。为了改善性能,我们想到了ConcurrentHashMap
public class Memoier2<K, V> implements Computable<K, V> {
	private final Map<K, V> cache = new ConcurrentHashMap<K, V>();

	private final Computable<K, V> c;

	public Memoier1(Computable<K, V> c) {
		this.c = c;
	}

	@Override
	public V compute(K key) throws InterruptedException {

		V result = cache.get(key);
		if (result == null) {
			result = c.compute(key);
			cache.put(key, result);
		}
		return result;

	}
}

这下我们利用了ConcurrentHashMap的特性,compute方法可以并行了。可是,这依然不完美,因为我们说了,compute方法可能很耗时。也就是说,当一个线程在调用compute方法时,另外一个线程也开始调用,但是他不知道前一个线程的状态。所以他也调用了compute方法,这样,缓存就变得无意义了。我们只希望compute一次。问题就出在线程之间没有一个状态可共享。后面的线程如何知道这个key上又一个相同的线程正在执行呢?
这是Future就派上用场了。Future可以捕获到线程执行的结果。

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Memoier3<K,V> implements Computable<K,V>{
	private final Map<K,Future<V>> cache = new ConcurrentHashMap<K,Future<V>>();
	private final Computable<K, V> c;
	
	public Memoier3(Computable<K,V> c){
		this.c = c;
	}

	@SuppressWarnings("rawtypes")
	@Override
	public V compute(final K k) throws InterruptedException {
		// TODO Auto-generated method stub
		Future<V> f = cache.get(k);
		if(f == null){
			 FutureTask<V> ft = new FutureTask<V>(new Callable<V>(){

				@Override
				public V call() throws Exception {
					return c.compute(k); 
				}
				 
			 });
			 cache.put(k, ft);
			 f = ft;
			 ft.run();
		}	
		try{
			return f.get();
		}catch(ExecutionException e){
			throw lanuderThrowable(e.getCause());
		}
	}
	
	public static RuntimeException lanuderThrowable(Throwable t){
		if( t instanceof RuntimeException ){
			return (RuntimeException)t;
		}else if(t instanceof Error){
			throw new Error();
		}else
			throw new IllegalStateException("Not Unchecked", t);
		
	}

}


这下看上去好多了。但是作者还是找出了一些问题来。首先,如果两个线程都访问compute函数,并且都处于
if ( f == null ){
   ...
 put(k,v)
}

这样的(check-then-act)的线程不安全中。其次,如果Future失败了,那么每次我们获取到的都是错误的结果(cache pollution),最后还有cache超时,新旧替换等问题(作者只是提一提,我们可以自己思考如何实现)
下面是“最终”代码
package book.jcp.basic;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Memoier<K, V> implements Computable<K, V> {
	private final ConcurrentHashMap<K, Future<V>> cache = new ConcurrentHashMap<K, Future<V>>();
	private final Computable<K, V> c;

	public Memoier(Computable<K, V> c) {
		this.c = c;
	}

	@SuppressWarnings("rawtypes")
	@Override
	public V compute(final K k) throws InterruptedException {
		while (true) {
			Future<V> f = cache.get(k);
			if (f == null) {
				FutureTask<V> ft = new FutureTask<V>(new Callable<V>() {

					@Override
					public V call() throws Exception {
						// TODO Auto-generated method stub
						return c.compute(k);
					}

				});
				f = cache.putIfAbsent(k, ft);
				if (f == null) {
					f = ft;
					ft.run();
				}
			}
			try {
				return f.get();
			} catch (CancellationException e) {
				cache.remove(k);
			} catch (ExecutionException e) {
				throw lanuderThrowable(e.getCause());
			}
		}
	}

	public static RuntimeException lanuderThrowable(Throwable t) {
		if (t instanceof RuntimeException) {
			return (RuntimeException) t;
		} else if (t instanceof Error) {
			throw new Error();
		} else
			throw new IllegalStateException("Not Unchecked", t);

	}

}



我们看到 ,解决第一个问题,使用了 putIfAbsent,使得这个点不会出现两次计算。而对于被取消(失败)的Future,则是采用捕获异常后移除Future来实现。
最后提到的,缓存过期,就只能大家自己想办法了。
我认为只能是添加一个租期参数,然后使用一个专门的线程来扫描缓存。
4
3
分享到:
评论
17 楼 lvwenwen 2012-11-28  
zhukewen_java 写道
lvwenwen 写道
zhukewen_java 写道
boblee2010 写道
zhukewen_java 写道
引用
虽然使用了 synchronized ,但是很不辛,这样的同步只会造成更低下的性能

我以为jcp是java cache programming. 想说的是,对于“只会造成更低下的性能”,只要有同步,性能就会比不使用同步低下。

设计得好的话,是否可以不用synchronized呢?

同步并不只有synchronized,有其它的,甚至还有硬件级的。但都是会有性能损耗的。

其他的不如有哪些

读读传说中的"jcp"你就知道了

传说中的lock readlock ,writelock.
16 楼 zhukewen_java 2012-11-28  
lvwenwen 写道
zhukewen_java 写道
boblee2010 写道
zhukewen_java 写道
引用
虽然使用了 synchronized ,但是很不辛,这样的同步只会造成更低下的性能

我以为jcp是java cache programming. 想说的是,对于“只会造成更低下的性能”,只要有同步,性能就会比不使用同步低下。

设计得好的话,是否可以不用synchronized呢?

同步并不只有synchronized,有其它的,甚至还有硬件级的。但都是会有性能损耗的。

其他的不如有哪些

读读传说中的"jcp"你就知道了
15 楼 lvwenwen 2012-11-28  
zhukewen_java 写道
boblee2010 写道
zhukewen_java 写道
引用
虽然使用了 synchronized ,但是很不辛,这样的同步只会造成更低下的性能

我以为jcp是java cache programming. 想说的是,对于“只会造成更低下的性能”,只要有同步,性能就会比不使用同步低下。

设计得好的话,是否可以不用synchronized呢?

同步并不只有synchronized,有其它的,甚至还有硬件级的。但都是会有性能损耗的。

其他的不如有哪些
14 楼 zhukewen_java 2012-11-28  
boblee2010 写道
zhukewen_java 写道
引用
虽然使用了 synchronized ,但是很不辛,这样的同步只会造成更低下的性能

我以为jcp是java cache programming. 想说的是,对于“只会造成更低下的性能”,只要有同步,性能就会比不使用同步低下。

设计得好的话,是否可以不用synchronized呢?

同步并不只有synchronized,有其它的,甚至还有硬件级的。但都是会有性能损耗的。
13 楼 airu 2012-11-27  
lvwenwen 写道
哥们,jcp 是什么

各位不好意思,这里的JCP是书名《Java Concurrency in Practice》的简写。一般来说,牛逼的书都要有这么一个简写。主要是对作者的膜拜。
12 楼 boblee2010 2012-11-27  
zhukewen_java 写道
引用
虽然使用了 synchronized ,但是很不辛,这样的同步只会造成更低下的性能

我以为jcp是java cache programming. 想说的是,对于“只会造成更低下的性能”,只要有同步,性能就会比不使用同步低下。

设计得好的话,是否可以不用synchronized呢?
11 楼 beyondyuefei 2012-11-27  
我们只希望compute一次。问题就出在线程之间没有一个状态可共享。后面的线程如何知道这个key上又一个相同的线程正在执行呢?
这是Future就派上用场了。Future可以捕获到线程执行的结果。


  我没看出 第一个 future的例子和 ConcurrentHashMap的那个例子有什么区别啊
10 楼 qzy927513 2012-11-27  
不错,支持!!!!
9 楼 zhukewen_java 2012-11-27  
引用
虽然使用了 synchronized ,但是很不辛,这样的同步只会造成更低下的性能

我以为jcp是java cache programming. 想说的是,对于“只会造成更低下的性能”,只要有同步,性能就会比不使用同步低下。
8 楼 zhuzl5210798 2012-11-27  
建议楼主把 Future 再讲下啊?
7 楼 bornku 2012-11-27  
lvwenwen 写道
哥们,jcp 是什么

jcp 就是 《java并发编程实践》这本书!!!!! 楼主有故作玄虚之嫌啊
6 楼 kidneyball 2012-11-27  
cnzxp521 写道
JCP(Java Community Process)成立于1998年,是使有兴趣的各方参与定义Java的特征和未来版本的正式过程。
JCP使用JSR(Java规范请求,Java Specification Requests)作为正式规范文档,描述被提议加入到Java体系中的的规范和技术。
JSR变为final状态前需要正式的公开审查,并由JCP Executive Committee投票决定。 最终的JSR会提供一个参考实现,它是免费而且公开源代码的;还有一个验证是否符合API规范的 Technology Compatibility Kit。


这里说的貌似是Java Concurrency Programming或者Java Concurrency in Practice之类的吧
5 楼 luciferdevil 2012-11-27  
以前公司的网站一天PV几千万,是个android market的网站,高峰期出现过400并发查数据库,最后采用了类似缓存备份的方式解决的,也用了同步,不过缓存的compute挺快,等待不是太久。http://www.cnblogs.com/freedom-elf/archive/2011/11/30/2269510.html
4 楼 cnzxp521 2012-11-26  
JCP(Java Community Process)成立于1998年,是使有兴趣的各方参与定义Java的特征和未来版本的正式过程。
JCP使用JSR(Java规范请求,Java Specification Requests)作为正式规范文档,描述被提议加入到Java体系中的的规范和技术。
JSR变为final状态前需要正式的公开审查,并由JCP Executive Committee投票决定。 最终的JSR会提供一个参考实现,它是免费而且公开源代码的;还有一个验证是否符合API规范的 Technology Compatibility Kit。
3 楼 lvwenwen 2012-11-26  
may小张 写道
写得很好!谢谢!分享!

jcp 是什么
2 楼 may小张 2012-11-26  
写得很好!谢谢!分享!
1 楼 lvwenwen 2012-11-26  
哥们,jcp 是什么

相关推荐

    java并发编程实战中文加英文版加源码

    本书作者都是Java Community Process JSR 166专家组(并发工具)的主要成员,并在其他很多JCP专家组里任职。Brian Goetz有20多年的软件咨询行业经验,并著有至少75篇关于Java开发的文章。Tim Peierls是“现代多...

    WLW_JCP2.zip

    标题 "WLW_JCP2.zip" 提供的信息表明这可能是一个关于WLW(可能是开发者或项目的简称)的项目,而“JCP2”可能是该项目的第二个版本或者特定部分的标识。描述简单地标注为“源文件”,暗示我们正在处理一个包含编程...

    JAVA并发编程实践.pdf

    本书作者系lava标准化组织(Java Cotl]munity Process)JSR 166专家组(并发工具)的主要成员,同时他们还致力于其他多个JCP专家组织。Brain Goetz是一位拥有二十年行业经验的软件咨询师,发表过超过75篇关于。Java开发...

    jcp:Java 并发实践

    Java 并发实践提炼 该存储库旨在存储组织在一本流行书籍讨论的想法、概念和问题的正在进行的工作的结果。 基本面 构建并发应用程序 活性、性能和测试 进阶课题 【Java内存模型】(the-java-memory-model.textile)

    Java并发编程实战

    本书作者都是Java Community Process JSR 166专家组(并发工具)的主要成员,并在其他很多JCP专家组里任职。Brian Goetz有20多年的软件咨询行业经验,并著有至少75篇关于Java开发的文章。 《Java并发编程实战》深入浅...

    杰表云打印 JCP 推出Webkit内核版

    杰表云打印 JCP 推出Webkit内核版,打印速度更快,功能更强 ! 1. 支持 CSS3 ,HTML 5 标签,如 Canvas,SVG; 2. 得力于js,渲染引擎速度提升,打印更快,对于JCP自动分页,速度提升近50%; 3. 对于同时使用国产系统...

    JCP011.py

    JCP011

    JCP035.py

    JCP035

    JCP073.py

    JCP073

    JCP007.py

    JCP007

    JCP057.py

    JCP057

    JCP008.py

    JCP008

    JCP002.py

    JCP002

    JCP048.py

    JCP048

    JCP072.py

    JCP072

    JCP010.py

    JCP010

    JCP060.py

    JCP060

    JCP071.py

    JCP071

    JCP003.py

    JCP003

    JCP022.py

    JCP022

Global site tag (gtag.js) - Google Analytics