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. 解决tomcat服务器下,只能通过localhost,而不能通过127.0.0.1或者本地ip地址访问的问题

    今天在tomcat上部署了一个web应用以后,发现用localhost的方式来访问应用是正常的,但是换成127.0.0.1或者是本地的ip地址来访问,确出现访问不了的情况.之前想是不是防火墙的问题,于 ...

  2. Android颜色值(RGB)所支持的四种常见形式

    Android中颜色值是通过红(Red).绿(Green).蓝(Blue)三原色,以及一个透明度(Alpha)值来表示的,颜色值总是以井号(#)开头,接下来就是Alpha-Red-Green-Blue ...

  3. ||在oracle数据库中起到字符串拼接的作用

    例子:select org.id from org where inner_code like '12011601001' || '%' ||在oracle数据库中起到字符串拼接的作用,上面等同于'1 ...

  4. django ---- models继承

    django 中各个models之前可以有继承关系.这种继承关系又可以分成三种情况: 1.简单继承 2.抽象继承 3.代理 一.简单继承: model定义 from django.db import ...

  5. Fast Algorithm To Find Unique Items in JavaScript Array

    When I had the requirement to remove duplicate items from a very large array, I found out that the c ...

  6. centos 7 下图形验证码乱码

    工作中遇到一个问题:同样的代码在centos 6.5下图形验证码是正常的 但是在centos 7下面是乱码 centos 6.5 的系统字体库目录 [wwwad@P2P-test2 fonts]$ p ...

  7. shell 计算时间差

    #!/bin/bash #date_5='awk 'BEGIN{print strftime("%H:%M",(systime()-300))}'' #ps -ef | grep ...

  8. php的opcode缓存

    前言:由php的运行机制决定,其实php在运行阶段我们也是可以进行缓存的从而提高程序运行效率,这就是我们常说的opcode缓存.1.简述php的运行机制(因为本文是写opcode缓存的所以这里只是简要 ...

  9. 未能加载文件或程序集“Microsoft.SqlServer.Management.Sdk.Sfc, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91”或它的某一个依赖项。系统找不到指定的文件。

    莫名其妙的,在 VS 中添加数据库连接就报这个错误,经过查找,解决方法是重新安装下两个sql server的组件:SharedManagementObjects.msi 和 SQLSysClrType ...

  10. Android 源码解析:单例模式-通过容器实现单例模式-懒加载方式

    本文分析了 Android 系统服务通过容器实现单例,确保系统服务的全局唯一. 开发过 Android 的用户肯定都用过这句代码,主要作用是把布局文件 XML 加载到系统中,转换为 Android 的 ...