BlockingCache是对Ehcache进行的扩展,BlockingCache内置了读写锁,不需要用户显示调用。

要彻底分析BlockingCache的原理,需要首先来看一下它内部用到的一些类。

//锁的管理器接口
public interface CacheLockProvider {
/**
* 根据key获取锁
* 这个实现需要保证给定相同的key必须返回同一把锁
*/
Sync getSyncForKey(Object key);
}
public interface StripedReadWriteLock extends CacheLockProvider {

    /**
* 根据key获得Java的ReadWriteLock
*/
ReadWriteLock getLockForKey(Object key); /**
* 获取所有锁
*/
List<ReadWriteLockSync> getAllSyncs(); }

上面的俩个接口是用来管理锁的,待会我们再看具体实现,再来看一下锁的接口。

public interface Sync {
/**
* Acquire lock of LockType.READ or WRITE
* @param type the lock type to acquire
*/
void lock(LockType type); /**
* Tries to acquire a LockType.READ or WRITE for a certain period
* @param type the lock type to acquire
* @param msec timeout
* @return true if the lock got acquired, false otherwise
* @throws InterruptedException Should the thread be interrupted
*/
boolean tryLock(LockType type, long msec) throws InterruptedException; /**
* Releases the lock held by the current Thread.
* In case of a LockType.WRITE, should the lock not be held by the current Thread, nothing happens
* @param type the lock type to acquire
*/
void unlock(LockType type); /**
* Returns true is this is lock is held at given level by the current thread.
*
* @param type the lock type to test
* @return true if the lock is held
*/
boolean isHeldByCurrentThread(LockType type);
}
/**
* 这个类是Sync接口的实现类,底层用到了Java原生的ReentrantReadWriteLock
* ReentrantReadWriteLock是读写锁,不了解的可以先去学习一下这个类的使用方法
* 不然会很难理解,其实ReadWriteLockSync的类的方法都是调用ReentrantReadWriteLock。
*/
public class ReadWriteLockSync implements Sync { //Java的读写锁,其实这个类底层都是调用它.
private final ReentrantReadWriteLock rrwl; public ReadWriteLockSync() {
this(new ReentrantReadWriteLock());
} public ReadWriteLockSync(ReentrantReadWriteLock lock) {
this.rrwl = lock;
}
/**
* 根据类型获取锁.
*/
public void lock(final LockType type) {
getLock(type).lock();
} /**
* 在给定时间内尝试获取锁.
*/
public boolean tryLock(final LockType type, final long msec) throws InterruptedException {
return getLock(type).tryLock(msec, TimeUnit.MILLISECONDS);
} /**
* 释放锁.
*/
public void unlock(final LockType type) {
getLock(type).unlock();
} //根据枚举类型获取写入锁OR读取锁.
private Lock getLock(final LockType type) {
switch (type) {
case READ:
return rrwl.readLock();
case WRITE:
return rrwl.writeLock();
default:
throw new IllegalArgumentException("We don't support any other lock type than READ or WRITE!");
}
} public ReadWriteLock getReadWriteLock() {
return rrwl;
} /**
* 判断当前线程是否获取了写入锁.
*/
public boolean isHeldByCurrentThread(LockType type) {
switch (type) {
case READ:
throw new UnsupportedOperationException("Querying of read lock is not supported.");
case WRITE:
return rrwl.isWriteLockedByCurrentThread();
default:
throw new IllegalArgumentException("We don't support any other lock type than READ or WRITE!");
}
}
}

StripedReadWriteLockSync类是负责管理锁的,会根据传入的key的hash值计算出数组的下标,原理与hashmap一样。

/**
* 这个类是锁管理器的实现类,用来管理锁.
* 这个类的实现原理就是内部维护一个锁的数组(默认2048个)
* 然后根据传入的key通过hash算法获取到一个int类型的值,这个值就是锁数组的下标.
* 这样就可以针对不同的key分别锁定提高并发效率
*/
public class StripedReadWriteLockSync implements StripedReadWriteLock { /**
* 默认锁的数量必须是2的幂
*/
public static final int DEFAULT_NUMBER_OF_MUTEXES = 2048; //锁的数组
private final ReadWriteLockSync[] mutexes;
//锁的list
private final List<ReadWriteLockSync> mutexesAsList; public StripedReadWriteLockSync() {
this(DEFAULT_NUMBER_OF_MUTEXES);
} /**
* 构造函数,初始化锁.
*/
public StripedReadWriteLockSync(int numberOfStripes) {
if ((numberOfStripes & (numberOfStripes - 1)) != 0) {
throw new CacheException("Cannot create a CacheLockProvider with a non power-of-two number of stripes");
}
if (numberOfStripes == 0) {
throw new CacheException("A zero size CacheLockProvider does not have useful semantics.");
} //初始化数组.
mutexes = new ReadWriteLockSync[numberOfStripes]; //初始化ReadWriteLockSync锁放入数组.
for (int i = 0; i < mutexes.length; i++) {
mutexes[i] = new ReadWriteLockSync();
}
mutexesAsList = Collections.unmodifiableList(Arrays.asList(mutexes));
} /**
* 根据key获取锁,相同的key获取到同一把锁。
*/
public ReadWriteLockSync getSyncForKey(final Object key) {
//根据key的hash算法在与数组的长度计算出数组下标,这个算法会保证得到的int值在0-数组长度之内不会越界.
int lockNumber = ConcurrencyUtil.selectLock(key, mutexes.length);
return mutexes[lockNumber];
} /**
* 根据key获取锁
*/
public ReadWriteLock getLockForKey(final Object key) {
int lockNumber = ConcurrencyUtil.selectLock(key, mutexes.length);
return mutexes[lockNumber].getReadWriteLock();
} /**
* Returns all internal syncs
* @return all internal syncs
*/
public List<ReadWriteLockSync> getAllSyncs() {
return mutexesAsList;
}
}

BlockingCache核心代码分析,在使用它的时候有一点需要注意,如果一个线程get方法获取element,当获取不到时会返回null,这时线程并没有释放写入锁,这点一定要注意。

所以必须要调用blockingCache.put(new Element(key, null)) 来释放锁。

/**
* A blocking decorator for an Ehcache, backed by a {@link Ehcache}.
*/
public class BlockingCache extends EhcacheDecoratorAdapter { /**
* The amount of time to block a thread before a LockTimeoutException is thrown
*/
protected volatile int timeoutMillis; private final int stripes; //线程安全的引用AtomicReference
private final AtomicReference<CacheLockProvider> cacheLockProviderReference; private final OperationObserver<GetOutcome> getObserver = operation(GetOutcome.class).named("get").of(this).tag("blocking-cache").build(); /**
* Creates a BlockingCache which decorates the supplied cache.
*/
public BlockingCache(final Ehcache cache, int numberOfStripes) throws CacheException {
super(cache);
this.stripes = numberOfStripes;
this.cacheLockProviderReference = new AtomicReference<CacheLockProvider>();
} /**
* Creates a BlockingCache which decorates the supplied cache.
*/
public BlockingCache(final Ehcache cache) throws CacheException {
this(cache, StripedReadWriteLockSync.DEFAULT_NUMBER_OF_MUTEXES);
} private CacheLockProvider getCacheLockProvider() {
CacheLockProvider provider = cacheLockProviderReference.get();
while (provider == null) {
cacheLockProviderReference.compareAndSet(null, createCacheLockProvider());
provider = cacheLockProviderReference.get();
}
return provider;
} //初始化StripedReadWriteLockSync
private CacheLockProvider createCacheLockProvider() {
Object context = underlyingCache.getInternalContext();
if (underlyingCache.getCacheConfiguration().isTerracottaClustered() && context != null) {
return (CacheLockProvider) context;
} else {
return new StripedReadWriteLockSync(stripes);
}
} /**
* Retrieve the EHCache backing cache
*/
protected Ehcache getCache() {
return underlyingCache;
} /**
* 获取元素
*/
@Override
public Element get(final Object key) throws RuntimeException, LockTimeoutException {
getObserver.begin(); //通过key获取锁,不同的key在大多数情况获取的锁都是不同的,所以性能会很好.
Sync lock = getLockForKey(key); //获取读取锁,读取锁是可以并发读的,所以效率会很好.
acquiredLockForKey(key, lock, LockType.READ); Element element;
try {
//在真正的cache里获取元素
element = underlyingCache.get(key);
} finally {
//释放读取锁
lock.unlock(LockType.READ);
} //如果元素为空,则获取写入锁,写入锁不可以并发进入的。
if (element == null) {
acquiredLockForKey(key, lock, LockType.WRITE); //再次获取元素,如果为空则直接返回,注意当前线程并没有释放锁.这里一定要注意。
element = underlyingCache.get(key);
if (element != null) {
//如果元素不为空,说明已经有线程在其它时刻put进去元素了,那么直接释放锁就OK了。
lock.unlock(LockType.WRITE);
getObserver.end(GetOutcome.HIT);
} else {
getObserver.end(GetOutcome.MISS_AND_LOCKED);
}
return element;
} else {
getObserver.end(GetOutcome.HIT);
return element;
}
} private void acquiredLockForKey(final Object key, final Sync lock, final LockType lockType) {
if (timeoutMillis > 0) {
try {
//如果设置了超时时间,那么在给定时间内获取不到锁就抛异常.
boolean acquired = lock.tryLock(lockType, timeoutMillis);
if (!acquired) {
StringBuilder message = new StringBuilder("Lock timeout. Waited more than ")
.append(timeoutMillis)
.append("ms to acquire lock for key ")
.append(key).append(" on blocking cache ").append(underlyingCache.getName());
throw new LockTimeoutException(message.toString());
}
} catch (InterruptedException e) {
throw new LockTimeoutException("Got interrupted while trying to acquire lock for key " + key, e);
}
} else {
//获取锁,获取不到就会一直等待知道获取到锁。
lock.lock(lockType);
}
} protected Sync getLockForKey(final Object key) {
return getCacheLockProvider().getSyncForKey(key);
} /**
* put元素自动释放锁,主要看doAndReleaseWriteLock.
*/
@Override
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());
}
return null;
}
});
} private <V> V doAndReleaseWriteLock(PutAction<V> putAction) { if (putAction.element == null) {
return null;
} Object key = putAction.element.getObjectKey(); //根据key获取锁.
Sync lock = getLockForKey(key); //判断一下当前线程是否已经获取了写入锁,如果已经获取到锁,那么说明当前线程是执行get方法是元素为null时获取到锁的.
//否则说明是另一个新的线程直接调用put的方法,所以需要重新获取锁.
if (!lock.isHeldByCurrentThread(LockType.WRITE)) {
lock.lock(LockType.WRITE);
}
try {
return putAction.put();
} finally {
//在这里释放锁.
lock.unlock(LockType.WRITE);
}
} private abstract static class PutAction<V> { private final Element element; private PutAction(Element element) {
this.element = element;
}
abstract V put();
}
}

Ehcache BlockingCache 源码分析的更多相关文章

  1. [转]RMI方式Ehcache集群的源码分析

    RMI方式Ehcache集群的源码分析   Ehcache不仅支持基本的内存缓存,还支持多种方式将本地内存中的缓存同步到其他使用Ehcache的服务器中,形成集群.如下图所示:   Ehcache支持 ...

  2. RMI方式Ehcache集群的源码分析

    Ehcache不仅支持基本的内存缓存,还支持多种方式将本地内存中的缓存同步到其他使用Ehcache的服务器中,形成集群.如下图所示: Ehcache支持多种集群方式,下面以RMI通信方式为例,来具体分 ...

  3. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  4. Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存源码分析

    像Mybatis.Hibernate这样的ORM框架,封装了JDBC的大部分操作,极大的简化了我们对数据库的操作. 在实际项目中,我们发现在一个事务中查询同样的语句两次的时候,第二次没有进行数据库查询 ...

  5. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

  6. MyBatis 源码分析系列文章合集

    1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...

  7. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  8. 【转】MaBatis学习---源码分析MyBatis缓存原理

    [原文]https://www.toutiao.com/i6594029178964673027/ 源码分析MyBatis缓存原理 1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 ...

  9. Mybatis源码分析之Cache二级缓存原理 (五)

    一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...

随机推荐

  1. [转]Redis几个认识误区

    转自timyang:http://timyang.net/data/redis-misunderstanding/ 前几天微博发生了一起大的系统故障,很多技术的朋友都比较关心,其中的原因不会超出Jam ...

  2. java-Spring 管理bean例子

    Spring 通过2种方式管理bean 首先要导入Spring的包,(Spring.jar和commonslogging.jar) 或加载分开的... 在src目录下建立applicationCont ...

  3. Guid ToString 格式

    转自 http://www.cnblogs.com/greenerycn/archive/2010/04/25/guid_tostring_format.html 在日常编程中,Guid是比较常用的, ...

  4. 如何分析Java程序中的死锁

    使用下面方式:产生java的Thread Dump信息 windows平台上:ctrl+break 或者 ctrl+(fn+b)键 Linux平台上:kill -3 pid (查找程序进程id -&g ...

  5. GCD 实现生产-消费 模式

    #import "ViewController.h" @interface ViewController (){ } @property (nonatomic,strong) di ...

  6. Java:多线程,Semaphore同步器

    1. 背景 类java.util.concurrent.Semaphore提供了一个计数信号量.通过Semaphore类,可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如 ...

  7. 使用JS生成二维码QRCode

    这其实很简单,项目中使用插件即可生成,主要有两种方式: 一种是在项目中使用java生成,把图片生成到某个目录,然后在用tomcat或者nginx虚拟一下路径即可访问,这种方式我们不用,因为会在目录中生 ...

  8. Atitit  404错误的排查流程总结 v3 qaf

    Atitit  404错误的排查流程总结 v3 qaf 1.1. 用了注解不生效 提示404 Not Found1 1.2. 路径不对了,开头多了个空格1 2. 500 Servlet Excepti ...

  9. 关于iPhone音频的那些事

    音频文件(Audio File) 1.有两个概念(1).文件格式(File Format or Audio Containers)——描述文件本身的格式,里面的音频数据可以用不同的编码格式.例如:ca ...

  10. Azure产品目录

    计算 Linux 虚拟机:为 Ubuntu.Red Hat 等预配虚拟机 Windows 虚拟机 为 SQL Server.SharePoint 等预配虚拟机 应用服务 快速创建适用于 Web 和移动 ...