现在开始要介绍的从缓存中读取数据的过程,还是在GeneralCacheAdministrator#getFromCache(),这里有3个同名方法,还是找一个参数最多的:
/**
* Get an object from the cache
*
* @param key
* The key entered by the user.
* @param refreshPeriod
* How long the object can stay in cache in seconds. To allow the
* entry to stay in the cache indefinitely, supply a value of
* {@link CacheEntry#INDEFINITE_EXPIRY}
* @param cronExpression
* A cron expression that the age of the cache entry will be
* compared to. If the entry is older than the most recent match
* for the cron expression, the entry will be considered stale.
* @return The object from cache
* @throws NeedsRefreshException
* when no cache entry could be found with the supplied key, or
* when an entry was found but is considered out of date. If the
* cache entry is a new entry that is currently being
* constructed this method will block until the new entry
* becomes available. Similarly, it will block if a stale entry
* is currently being rebuilt by another thread and cache
* blocking is enabled (<code>cache.blocking=true</code>).
*/
public Object getFromCache(String key, int refreshPeriod, String cronExpression)
throws NeedsRefreshException {
return getCache().getFromCache(key, refreshPeriod, cronExpression);
}
还是来到了Cache的#getFromCache():
public Object getFromCache(String key, int refreshPeriod, String cronExpiry) throws NeedsRefreshException {
// 先尝试在缓存中查找
CacheEntry cacheEntry = this.getCacheEntry(key, null, null);
// 实际缓存的对象
Object content = cacheEntry.getContent();
// 缓存访问事件类型,默认是"缓存命中"
CacheMapAccessEventType accessEventType = CacheMapAccessEventType.HIT;
boolean reload = false;
// 判断缓存是否过期
if (this.isStale(cacheEntry, refreshPeriod, cronExpiry)) {
// 得到缓存更新状态
EntryUpdateState updateState = getUpdateState(key);
try {
synchronized (updateState) {
if (updateState.isAwaitingUpdate() || updateState.isCancelled()) {
// 如果是等待更新或者取消更新,那么将更新状态设置成UPDATE_IN_PROGRESS
updateState.startUpdate();
if (cacheEntry.isNew()) {// 如果缓存中没有,那么设成"没命中"
accessEventType = CacheMapAccessEventType.MISS;
} else {// 设成"命中过期数据",这个后边要抛异常的
accessEventType = CacheMapAccessEventType.STALE_HIT;
}
} else if (updateState.isUpdating()) {
// 如果其他线程正在更新缓存中,那么wait,等到更新完成后notify
if (cacheEntry.isNew() || blocking) {
do {
try {
updateState.wait();
} catch (InterruptedException e) {
}
} while (updateState.isUpdating());
if (updateState.isCancelled()) {
// 如果更新的线程取消了更新,那么缓存仍然是过期的,执行和第一个分支相同的操作
updateState.startUpdate();
if (cacheEntry.isNew()) {
accessEventType = CacheMapAccessEventType.MISS;
} else {
accessEventType = CacheMapAccessEventType.STALE_HIT;
}
} else if (updateState.isComplete()) {
reload = true;
} else {
log.error("Invalid update state for cache entry " + key);
}
}
} else {
reload = true;
}
}
} finally {
// 线程引用数减少1
releaseUpdateState(updateState, key);
}
}
if (reload) {
// 可以重新载入缓存中的数据了
cacheEntry = (CacheEntry) cacheMap.get(key);
if (cacheEntry != null) {
content = cacheEntry.getContent();
} else {
log.error("Could not reload cache entry after waiting for it to be rebuilt");
}
}
// 发送缓存事件
dispatchCacheMapAccessEvent(accessEventType, cacheEntry, null);
// 如果数据过期,抛出需要刷新数据的异常
if (accessEventType != CacheMapAccessEventType.HIT) {
throw new NeedsRefreshException(content);
}
return content;
}
整个流程还是很好理解的,这里看一些细节,首先是#isStale()方法:
protected boolean isStale(CacheEntry cacheEntry, int refreshPeriod, String cronExpiry) {
// 判断是否需要刷新
boolean result = cacheEntry.needsRefresh(refreshPeriod) || isFlushed(cacheEntry);
if ((!result) && (cronExpiry != null) && (cronExpiry.length() > 0)) {
try {
// 利用计划任务的方式处理过期
FastCronParser parser = new FastCronParser(cronExpiry);
result = result || parser.hasMoreRecentMatch(cacheEntry.getLastUpdate());
} catch (ParseException e) {
log.warn(e);
}
}
return result;
}
其次是获取更新状态的方法:
protected EntryUpdateState getUpdateState(String key) {
EntryUpdateState updateState;
synchronized (updateStates) {
// Try to find the matching state object in the updating entry map.
updateState = (EntryUpdateState) updateStates.get(key);
if (updateState == null) {
// It's not there so add it.
updateState = new EntryUpdateState();
updateStates.put(key, updateState);
} else {
// Otherwise indicate that we start using it to prevent its
// removal until all threads are done with it.
updateState.incrementUsageCounter();
}
}
return updateState;
}
另外就是EntryUpdateState的startUpdate()方法,它会把过期缓存的状态设为UPDATE_IN_PROGRESS,这样更新缓存的线程就会执行更新操作了。
public int startUpdate() {
if ((state != NOT_YET_UPDATING) && (state != UPDATE_CANCELLED)) {
throw new IllegalStateException("Cannot begin cache update - current state (" + state
+ ") is not NOT_YET_UPDATING or UPDATE_CANCELLED");
}
state = UPDATE_IN_PROGRESS;
return incrementUsageCounter();
}
整个流程还是很好理解的,首先判断缓存是否过期,如果过期,那么会从更新状态缓存中查找更新状态,如果没查到那么创建一个,如果已经存在了,将引用计数加1。
如果缓存还没有进行更新,那么将更新状态设置为UPDATE_IN_PROGRESS,并且再次将引用计数加1,此时:
(1) 在设定完更新状态后,读取缓存的线程会将引用计数减1,如果没有线程此时再引用了那么在更新状态缓存中移除此项。
(2) 如果其他线程在更新缓存的时候会将引用计数减1,如果没有线程此时再引用了那么在更新状态缓存中移除此项。
这样,通过上边的两个过程,就将引用计数变为0了。
想法其实是非常好的,不过我在实际的测试中发现有时候其实并不是完全按照这个流程走下去的(当然很有可能是当初设计就是如此,允许发生报出这种异常):
线程1调用getFromCache()并发现缓存过期,更新状态为UPDATE_IN_PROGRESS后还没有调用#releaseUpdateState(),线程2执行#putInCache(),线程3执行#putInCache()。线程2更新完状态为UPDATE_COMPLETE后将引用计数减1,但是此时由于线程1还有一个引用计数,所以线程3更新状态时会在#completeUpdate()中抛出异常,因为此时的状态是UPDATE_COMPLETE。