今天学习一下Mybatis的cache包的源码,理解BlockingCache缓存实现
1、贴源码+注释
/**
* Copyright 2009-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.cache.decorators;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
import org.apache.ibatis.cache.impl.PerpetualCache;
/**
* 简单阻塞缓存装饰类
* 这是一个简单低效EhCache阻塞缓存版本,如果某个缓存key的值不存在就会锁定该缓存,这些线程
* 将会等到这个key对应值被设置进来,而不会去查询数据库
* 1、他没有实现getReadWriteLock方法
* 2、没有值,它会一直用ReentrantLock 锁定,直到该线程主动释放这个锁,否则一直占用,其他线程是不可以解锁,解铃还须系铃人
* Simple blocking decorator
*
* Simple and inefficient version of EhCache's BlockingCache decorator.
* It sets a lock over a cache key when the element is not found in cache.
* This way, other threads will wait until this element is filled instead of hitting the database.
*
* @author Eduardo Macarron
*
*/
public class BlockingCache implements Cache {
/**
* 超时时间
*/
private long timeout;
/**
* 委托缓存
*/
private final Cache delegate;
/**
* 通过并行hashMap存储ReentrantLock对象
*/
private final ConcurrentHashMap<Object, ReentrantLock> locks;
/**
* 传入委托缓存
* @param delegate
*/
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<>();
}
/**
* 获取委托缓存Id
* @return
*/
@Override
public String getId() {
return delegate.getId();
}
/**
* 委托缓存大小
* @return
*/
@Override
public int getSize() {
return delegate.getSize();
}
/**
* 设置缓存
* @param key Can be any object but usually it is a {@link CacheKey}
* @param value The result of a select.
*/
@Override
public void putObject(Object key, Object value) {
try {
delegate.putObject(key, value);
} finally {
releaseLock(key);
}
}
/**
* 获取缓存对象
* @param key The key
* @return
*/
@Override
public Object getObject(Object key) {
//获取锁
//如果对象有值释放锁
//不然一直占用锁
acquireLock(key);
Object value = delegate.getObject(key);
if (value != null) {
releaseLock(key);
}
return value;
}
/**
* 手动释放锁,当前线程暂用的锁
* @param key The key
* @return
*/
@Override
public Object removeObject(Object key) {
// despite of its name, this method is called only to release locks
releaseLock(key);
return null;
}
@Override
public void clear() {
delegate.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
/**
* 通过key 获取ReentrantLock,没有就新增
* @param key
* @return
*/
private ReentrantLock getLockForKey(Object key) {
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
private void acquireLock(Object key) {
//获取lock,
//是否设置超时时间,有的就尝试lock(毫秒),否则直接锁定
Lock lock = getLockForKey(key);
if (timeout > 0) {
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();
System.out.println("加锁 getThread : " + Thread.currentThread().getName());
}
}
/**
* 释放锁, 锁定key
* @param key
*/
private void releaseLock(Object key) {
// 获取当前缓存key,value 对应锁, ReentrantLock
//判断该key对应锁是否为当前线程占用,有则释放
ReentrantLock lock = locks.get(key);
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("释放锁 getThread : " + Thread.currentThread().getName());
}
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* 测试main方法
* ReentrantLock 表示重入锁,同一个线程加锁几次,需要解锁几次,例如加锁10次,解锁也要10次,否则其他线程不能获取锁
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
BlockingCache blockingCache = new BlockingCache(new PerpetualCache("111111"));
String key = "2222";
Thread putThread = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
blockingCache.putObject(key, "111111");
});
putThread.start();
// 一直到获取对象的值才结束,然后统一释放锁
Thread getThread = new Thread(()->{
int unlockCount = 0;
long start = System.currentTimeMillis();
System.out.println("开始时间:" + start + ", getThread : " + Thread.currentThread().getName());
while (blockingCache.getObject(key) == null) {
unlockCount++;
}
System.out.println("结束时间:" + (System.currentTimeMillis() - start) + ", getThread : " + Thread.currentThread().getName());
//释放锁
for (int i = 0; i< unlockCount; i++) {
blockingCache.releaseLock(key);
}
});
getThread.start();
//尝试获取这个锁,发现被getThread占用了,只能排队了
Thread tryLock = new Thread(()->{
//让tryLock线程慢于getThread线程
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
long start = System.currentTimeMillis();
System.out.println("开始时间:" + start + ", tryLock : " + Thread.currentThread().getName());
blockingCache.getObject(key);
System.out.println("结束时间:" + (System.currentTimeMillis() - start) + ", tryLock : " + Thread.currentThread().getName());
});
tryLock.start();
putThread.join();
getThread.join();
tryLock.join();
}
}
2、总结
- 这个类使用ReentrantLock进行加锁
- 解铃还须系铃人,thread自己加锁,也要自己解锁,否则其他线程一直处于等待状态
- 使用getObject(key)获取对应key的缓存对象,如果对象不存在就会直接返回null,但是该线程加了锁,必须要该线程主动释放锁,其他线程才可以获取getObject(key)的的缓存对象
- 每一个缓存key都有一个锁(ReentrantLock),不会相互影响