Ehcache(08)——可阻塞的Cache—BlockingCache
时间:2022-04-03 12:55
可阻塞的Cache—BlockingCache
在上一节我们提到了显示使用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只是对其进行了封装,使其支持并发操作。
Java代码
- 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所写)