现在开始要介绍的从缓存中读取数据的过程,还是在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。
306

被折叠的 条评论
为什么被折叠?



