买了一本Mybatis技术内幕,学一学源码,记录一下缓存模块中BlockingCache这个类
Mybatis缓存模块运用了装饰器模式
其中decorators包下类均为装饰器,impl包下唯一的PrepetualCache类为Cache的基本实现。源码比较简单,在实现Cache接口的同时维护了一个HashMap和id。
Cache源码:
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();//myabtis内部未使用此方法
ReadWriteLock getReadWriteLock();//从3.2.6版本开始myabtis内部不再使用该方法,缓存所需的任何锁定都必须由缓存提供程序内部提供
}
PrepetualCache类源码:
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
本文主角BlockingCache源码:
public class BlockingCache implements Cache {
private long timeout;//tryLock超时时间
private final Cache delegate;//被装饰的对象
private final ConcurrentHashMap<Object, ReentrantLock> locks;//维护了key和lock的对应关系
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<Object, ReentrantLock>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object value) {
try {
delegate.putObject(key, value);//调用被装饰对象putObject方法缓存
} finally {
releaseLock(key);//释放key对应锁,重要!
}
}
@Override
public Object getObject(Object key) {
acquireLock(key);//尝试获取key对应的锁
Object value = delegate.getObject(key);//调用被装饰对象getObject获取key对应的value值
if (value != null) {//注意,若value值为null,不会释放锁!这表明我们必须通过putObject方法添加key,value,才可以间接释放锁。
releaseLock(key);
}
return value;
}
@Override
public Object removeObject(Object key) {
// despite of its name, this method is called only to release locks
//仅仅是释放锁,不会移除key对应的value
releaseLock(key);
return null;
}
@Override
public void clear() {
delegate.clear();//调用被装饰对象清空缓存
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
private ReentrantLock getLockForKey(Object key) {//该方法在acquireLock()中被调用,我们必须通过getObject()才可以创建key对应锁,若直接调用putObject()且key对应的锁不存在时会抛出NPE
ReentrantLock lock = new ReentrantLock();
ReentrantLock previous = locks.putIfAbsent(key, lock);//不存在key时put
return previous == null ? lock : previous;
}
private void acquireLock(Object key) {
Lock lock = getLockForKey(key);
if (timeout > 0) {//timeout值通过set方法设置
try {
boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} catch (InterruptedException e) {
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
} else {
lock.lock();
}
}
private void releaseLock(Object key) {
ReentrantLock lock = locks.get(key);
if (lock.isHeldByCurrentThread()) {//这里可能会抛NPE,若先调用putObject,且key对应锁不存在时
lock.unlock();
}
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
测试示例:
package com.mybatis.coding.main;
import org.apache.ibatis.cache.decorators.BlockingCache;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.junit.Test;
import java.util.Objects;
public class CacheTest {
@Test
public void blockingCacheTest() throws Exception {
String key = "name";
String value = "zhangsan";
final PerpetualCache delegate = new PerpetualCache("shenma");
final BlockingCache cache = new BlockingCache(delegate);
final Thread get = new Thread(() -> {
System.out.println("get begin");
final Object object = cache.getObject(key);
System.out.println("get end object:" + object);
if (Objects.isNull(object)) {
//若get出的值为null,则一定要put对应key和value,否则将导致锁一直被占用无法解锁,阻塞其他线程访问缓存
cache.putObject(key, value);
System.out.println("putObject key:" + key + " value:" + value);
}
});
get.start();
final Thread get2 = new Thread(() -> {
sleep(500);
System.out.println("get2 begin");
final Object object = cache.getObject(key);
System.out.println("get2 end object:" + object);
});
get2.start();
get.join();
get2.join();
}
private void sleep(long sleepTime) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}