http://haohaoxuexi.iteye.com/blog/2119737

可阻塞的CacheBlockingCache

在上一节我们提到了显示使用Ehcache锁的问题,其实我们还可以隐式的来使用Ehcache的锁,那就是通过BlockingCache。BlockingCache是Ehcache的一个封装类,可以让我们对Ehcache进行并发操作。其内部的锁机制是使用的net.sf.ehcache.concurrent.ReadWriteLockSync,与显示锁调用是一样的实现,而ReadWriteLockSync内部使用的是Java的java.util.concurrent.locks.ReadWriteLock。

BlockingCache拥有两个构造函数,它们都接收一个Ehcache对象,其中一个还接收一个指定并发数量的参数numberOfStripes,另一个没有numberOfStripes参数,但其将使用默认值,默认值为2048。numberOfStripes的值必须大于0,且为2的指数。接收的参数cache表示真正进行操作的Ehcache对象,BlockingCache只是对其进行了封装,使其支持并发操作。

public BlockingCache(final Ehcache cache, int numberOfStripes) throws CacheException {
super(cache);
this.stripes = numberOfStripes;
this.cacheLockProviderReference = new AtomicReference<CacheLockProvider>();
} public BlockingCache(final Ehcache cache) throws CacheException {
this(cache, StripedReadWriteLockSync.DEFAULT_NUMBER_OF_MUTEXES);
}

虽然我们的Ehcache是支持锁调用的,但BlockingCache与显示锁调用不同的是它不是直接通过所持有的Ehcache来获取锁和释放锁的,而是由其内部完成的。在从BlockingCache中get元素时,是支持并发读的,这没有问题,但如果对应key对应的元素不存在,则线程将被阻塞,直到调用了对应的put()方法存放了一个对应的key的元素为止。这是怎么做到的呢?我们来看一下BlockingCache的源码。

public Element get(final Object key) throws RuntimeException, LockTimeoutException {
getObserver.begin();
Sync lock = getLockForKey(key);
acquiredLockForKey(key, lock, LockType.READ);
Element element;
try {
element = underlyingCache.get(key);
} finally {
lock.unlock(LockType.READ);
}
if (element == null) {
acquiredLockForKey(key, lock, LockType.WRITE);
element = underlyingCache.get(key);
if (element != null) {
lock.unlock(LockType.WRITE);
getObserver.end(GetOutcome.HIT);
} else {
getObserver.end(GetOutcome.MISS_AND_LOCKED);
}
return element;
} else {
getObserver.end(GetOutcome.HIT);
return element;
}
}

上述是BlockingCache的核心get()方法。我们可以看到首先我们获取到了对应key的Sync,Sync是一个接口,其实现类通过持有的Lock对象可以对对应的key进行Read Lock或Write Lock。另外有一点需要注意的是对于同一个key而言,我们使用的是同一个Lock对象。通过上一节对Ehcache显示锁介绍,我们知道Read Lock之间是不会阻塞的。所以当我们在试图get一个元素时:

1、如果对应的key没有Write锁,那我们的代码都可以顺利的执行到判断element是否为null那一行,这个时候获取到的Read锁已经释放,不会影响以后获取其Write锁;

2、如果对应key存在Write锁,则在调用“acquiredLockForKey(key, lock, LockType.READ);”时就会被阻塞直到对应的Write锁被释放。

3、如果获取到的element不为null,则将直接返回。

4、如果element为null,则将获取对应key的Write锁,此时如果存在其它线程获取了该key的Read锁,则将阻塞直到不存在对应的Read锁。

5、获取到Write锁以后重新get一次对应的元素,因为有可能在判断element为null之后,获取对应的Write锁之前,已经有线程put了一个对应的元素到Cache中。如果获取到了对应的元素则释放对应的Write锁,然后返回获取到的元素。

6、获取到了Write锁之后其它试图获取对应key的Read锁或Write锁的线程都将被阻塞。如果获取到了Write锁之后对应的元素还是为null,则将直接返回。此时除该线程以外的其它调用了get()方法的线程都将被阻塞,因为当前线程的Write锁还没有释放。

通过上面的代码和分析我们知道,如果在利用BlockingCache的get()方法获取一个元素时,如果对应的元素不存在,则除最终获取到Write锁的线程以外的线程都将被阻塞,而获取到了对应key的Write锁的线程该如何释放其Write锁呢?这是通过往BlockingCache中put一个对应key的元素来释放的。BlockingCache是实现了Ehcache接口的,所以Ehcache拥有的put*()方法,BlockingCache都有,但是在BlockingCache的put*()方法中都加入了一个doAndReleaseWriteLock的逻辑。我们先来看一个put()方法的实现。

public void put(final Element element) {  

    doAndReleaseWriteLock(new PutAction<Void>(element) {
@Override
public Void put() {
if (element.getObjectValue() != null) {
underlyingCache.put(element);
} else {
underlyingCache.remove(element.getObjectKey());
}
returnnull;
}
});
}

我们可以看到在该put()方法内部调用了一个doAndReleaseWriteLock()方法,从该方法名以及其接收的参数我们可以看出,doAndReleaseWriteLock()方法的作用就是执行接收的参数PutAction的put()方法,然后释放对应key的Write锁,而且PutAction的构造是接收一个Element参数的,这样在PutAction中的put()方法中我们就可以使用该Element对象了。doAndReleaseWriteLock()方法的实现如下所示。

private <V> V doAndReleaseWriteLock(PutAction<V> putAction) {  

    if (putAction.element == null) {
returnnull;
}
Object key = putAction.element.getObjectKey();
Sync lock = getLockForKey(key);
if (!lock.isHeldByCurrentThread(LockType.WRITE)) {
lock.lock(LockType.WRITE);
}
try {
return putAction.put();
} finally {
//Release the writelock here. This will have been acquired in the get, where the element was null
lock.unlock(LockType.WRITE);
}
}

从源代码我们可以看到,其内部实现跟我们设想的差不多。在PutAction所持有的Element不为null的情况下会判断当前线程是否持有对应key的Write锁,如果没有对应key的Write锁,则将试图获取其Write锁,这个时候如果该key的Write锁已经被别的线程获取了,则在这里将进行阻塞。拥有了Write锁之后就可以执行PutAction对象的put()方法了,执行完后就可以释放对应key的Write锁了。

回过头来看,之前从BlockingCache中get元素时,如果对应元素不存在,则该线程将获取到对应key的Write锁(并发情况下,究竟是哪一个线程会获取到该key的Write锁是不定的),将使其它试图获取该key的Write锁或Read锁的线程阻塞。如果该线程此时往BlockingCache中put一个对应key的元素,则该线程所持有的Write锁将会释放,其它线程可以顺利的获取该key的Read锁和Write锁,即可以顺利的调用BlockingCache的get()方法获取对应的元素。BlockingCache就是为使用页面缓存而设计的,当多个线程同时请求一个页面时,如果缓存中存在对应的页面,则可以直接返回,Read锁之间不会阻塞;如果对应的页面不存在,那么这个时候只有一个线程会返回null,其它线程都将被阻塞,返回值为null时,Ehcache将会把对应的页面put到BlockingCache中,此时该线程所持有的Write锁将释放,而其它被阻塞的线程也将可以顺利的获取到该页面。这样一来就可以避免多个线程在get到的元素为null时,都同时往缓存中put对应的页面,造成不必要的资源浪费。如果有页面缓存这样的需求的话使用BlockingCache是再合适不过了。关于Ehcache使用页面缓存的更多信息将在下一篇博文中介绍。

刚刚说的BlockingCache就是为页面缓存设计的。如果用户需要自己使用BlockingCache时注意在获取到的元素为null时要释放对应的Write锁。这个时候有两种方法,一是调用BlockingCache的任意put方法,往其中存放一个对应key的元素;二是自己定义一个类继承BlockingCache,然后开放一个释放锁的方法,对应逻辑可以参考BlockingCache的doAndReleaseLock()方法,这是因为其内部获取锁的方法getLockForKey()的访问类型是protected。

此外,BlockingCache在获取锁时如果被阻塞了,那么阻塞时间是不定的,它有可能会非常长。如果不希望阻塞时间太长的话,我们可以通过BlockingCache的setTimeoutMillis()方法设置最长阻塞时间,单位为毫秒,这样如果一个线程在timeoutMillis时间内还没有获取到对应的锁则将抛出LockTimeoutException。

(注:本文是基于Ehcache2.8.1所写)

Ehcache(08)——可阻塞的Cache——BlockingCache的更多相关文章

  1. HTML5学习总结-08 应用缓存(Application Cache)

    一 应用缓存(Application Cache) 1 应用缓存 HTML5 引入了应用程序缓存,这意味着 web 应用可进行缓存,并可在没有因特网连接时进行访问. 应用程序缓存为应用带来三个优势: ...

  2. Ehcache(2.9.x) - API Developer Guide, Cache Exception Handlers

    About Exception Handlers By default, most cache operations will propagate a runtime CacheException o ...

  3. Caused by: org.hibernate.boot.registry.selector.spi.StrategySelectionException: Unable to resolve name [org.hibernate.cache.ehcache.EhCacheRegionFactory] as strategy [org.hibernate.cache.spi.RegionFac

    警告: Exception encountered during context initialization - cancelling refresh attempt: org.springfram ...

  4. java Cache框架

    Cache框架乱炖   各类开源的缓存解决方案 JBossCache/TreeCacheJBossCache是一个复制的事务处理缓存,它允许你缓存企业级应用数据来更好的改善性能.缓存数据被自动复制,让 ...

  5. spring ehcache 页面、对象缓存

    一.Ehcache基本用法 CacheManager cacheManager = CacheManager.create(); // 或者 cacheManager = CacheManager.g ...

  6. Ehcache 整合Spring 使用页面、对象缓存

    Ehcache 整合Spring 使用页面.对象缓存 Ehcache在很多项目中都出现过,用法也比较简单.一 般的加些配置就可以了,而且Ehcache可以对页面.对象.数据进行缓存,同时支持集群/分布 ...

  7. (转)Ehcache 整合Spring 使用页面、对象缓存

    Ehcache在很多项目中都出现过,用法也比较简单.一般的加些配置就可以了,而且Ehcache可以对页面.对象.数据进行缓存,同时支持集群/分布式缓存.如果整合Spring.Hibernate也非常的 ...

  8. 缓存插件 EHCache 页面缓存CachingFilter

    Ehcache基本用法 CacheManager cacheManager = CacheManager.create(); // 或者 cacheManager = CacheManager.get ...

  9. EhCache 分布式缓存/缓存集群

    开发环境: System:Windows JavaEE Server:tomcat5.0.2.8.tomcat6 JavaSDK: jdk6+ IDE:eclipse.MyEclipse 6.6 开发 ...

随机推荐

  1. 实现输出h264直播流的rtmp服务器

    RTMP(Real Time Messaging Protocol)是常见的流媒体协议,用来传输音视频数据,结合flash,广泛用于直播.点播.聊天等应用,以及pc.移动.嵌入式等平台,是做流媒体开发 ...

  2. javac 命令用法

    引用自己写的Class 在java中手动编译时,总提示找不到类,调试成功后,特把目录结构与编译成功的命令列出: 样例一: 文件名 MessageStore.java Hello.java 源码 pac ...

  3. Ubuntu跬步之图片管理digiKam

    Ubuntu下自带了Image Viewer,具备基本的图片浏览功能. 偶尔有些图片需要分组,添加标注,幻灯片浏览等,所以找了款图片管理软件digiKam. 安装 Ubuntu Software Ce ...

  4. android中的style部分属性值介绍

    转自:http://blog.sina.com.cn/s/blog_70c759fd01013phv.html Android平台定义的主题样式: android:theme="@andro ...

  5. 使用异步任务加载网络上json数据并加载到ListView中

    Android中使用网络访问来加载网上的内容,并将其解析出来加载到控件中,是一种很常见的操作.但是Android的UI线程(也就是主线程)中是不允许进行耗时操作的,因为耗时操作会阻塞主线程,影响用户体 ...

  6. hibernate建表一对多 一的一方控制多的方

    级联操作,操作class对象的时候 级联操作 student Classes.java文件 package cn.itcast.hiberate.sh.domain; import java.util ...

  7. 【C++】统计代码覆盖率(二)

    嗷嗷嗷!!!好激动,我好蠢.不过最后还是解决了.呜呜呜 有些都是东一块西一块查的,如果有侵权欢迎私信我,我注明出处. 一 gcov&CMake 昨天试了下测试代码和被测代码都是c++的情况,直 ...

  8. Drupal如何更新注册表?

    Drupal的注册表是指registry和registry_file两个数据表.前一个表保存所有可用的类和接口以及它们所对应的文件,后一个表保存每个文件的hash码. 1. 将所有需要更新的文件都汇总 ...

  9. kali 安装完成后,无法进入界面

    vmware 下安装 kali-1.9 ,安装完成后,无法进入界面,提示: 系统出错且无法恢复,请联系管理员 解决办法如下: 在新建虚拟机的时候,选择客户端系统:linux   Debian 7. 因 ...

  10. HLS -- m3u8档案格式解析

    1. Playlist file 一个M3U的 Playlist 就是一个由多个独立行组成的文本文件,每行由回车/换行区分.每一行可以是一个URI.空白行或 是以”#“号开头的字符串,并且空格只能存在 ...