oscache源码浅析
oscache作为本地缓存框架,存储模型依然是通用的缓存键值对模型。oscache使用HashTable存放数据,我们看下源码:
GeneralCacheAdministrator:
/** * 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} * @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) throws NeedsRefreshException { return getCache().getFromCache(key, refreshPeriod); }
Cache:
/** * Retrieve an object from the cache specifying its key. * * @param key Key of the object in the cache. * @param refreshPeriod How long before the object needs refresh. To * allow the object to stay in the cache indefinitely, supply a value * of {@link CacheEntry#INDEFINITE_EXPIRY}. * @param cronExpiry A cron expression that specifies fixed date(s) * and/or time(s) that this cache entry should * expire on. * * @return The object from cache * * @throws NeedsRefreshException Thrown when the object either * doesn't exist, or exists but is stale. When this exception occurs, * the CacheEntry corresponding to the supplied key will be locked * and other threads requesting this entry will potentially be blocked * until the caller repopulates the cache. If the caller choses not * to repopulate the cache, they <em>must</em> instead call * {@link #cancelUpdate(String)}. */ 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; // Check if this entry has expired or has not yet been added to the cache. If // so, we need to decide whether to block, serve stale content or throw a // NeedsRefreshException if (this.isStale(cacheEntry, refreshPeriod, cronExpiry)) { //Get access to the EntryUpdateState instance and increment the usage count during the potential sleep EntryUpdateState updateState = getUpdateState(key); try { synchronized (updateState) { if (updateState.isAwaitingUpdate() || updateState.isCancelled()) { // No one else is currently updating this entry - grab ownership updateState.startUpdate(); if (cacheEntry.isNew()) { accessEventType = CacheMapAccessEventType.MISS; } else { accessEventType = CacheMapAccessEventType.STALE_HIT; } } else if (updateState.isUpdating()) { // Another thread is already updating the cache. We block if this // is a new entry, or blocking mode is enabled. Either putInCache() // or cancelUpdate() can cause this thread to resume. if (cacheEntry.isNew() || blocking) { do { try { updateState.wait(); } catch (InterruptedException e) { } } while (updateState.isUpdating()); if (updateState.isCancelled()) { // The updating thread cancelled the update, let this one have a go. // This increments the usage count for this EntryUpdateState instance 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 { //Make sure we release the usage count for this EntryUpdateState since we don't use it anymore. If the current thread started the update, then the counter was //increased by one in startUpdate() releaseUpdateState(updateState, key); } } // If reload is true then another thread must have successfully rebuilt the cache entry 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 we didn't end up getting a hit then we need to throw a NRE if (accessEventType != CacheMapAccessEventType.HIT) { throw new NeedsRefreshException(content); } return content; }
继续进入getCacheEntry方法:
/** * Get an entry from this cache or create one if it doesn't exist. * * @param key The key of the cache entry * @param policy Object that implements refresh policy logic * @param origin The origin of request (optional) * @return CacheEntry for the specified key. */ protected CacheEntry getCacheEntry(String key, EntryRefreshPolicy policy, String origin) { CacheEntry cacheEntry = null; // Verify that the key is valid if ((key == null) || (key.length() == 0)) { throw new IllegalArgumentException("getCacheEntry called with an empty or null key"); } cacheEntry = (CacheEntry) cacheMap.get(key); // if the cache entry does not exist, create a new one if (cacheEntry == null) { if (log.isDebugEnabled()) { log.debug("No cache entry exists for key='" + key + "', creating"); } cacheEntry = new CacheEntry(key, policy); } return cacheEntry; }
跟到这里,终于出现正主cacheMap:
/** * The actual cache map. This is where the cached objects are held. */ private AbstractConcurrentReadCache cacheMap = null;
我们看下这个类AbstractConcurrentReadCache:
/** * A version of Hashtable that supports mostly-concurrent reading, but exclusive writing. * Because reads are not limited to periods * without writes, a concurrent reader policy is weaker than a classic * reader/writer policy, but is generally faster and allows more * concurrency. This class is a good choice especially for tables that * are mainly created by one thread during the start-up phase of a * program, and from then on, are mainly read (with perhaps occasional * additions or removals) in many threads. If you also need concurrency * among writes, consider instead using ConcurrentHashMap. * <p> * * Successful retrievals using get(key) and containsKey(key) usually * run without locking. Unsuccessful ones (i.e., when the key is not * present) do involve brief synchronization (locking). Also, the * size and isEmpty methods are always synchronized. * * <p> Because retrieval operations can ordinarily overlap with * writing operations (i.e., put, remove, and their derivatives), * retrievals can only be guaranteed to return the results of the most * recently <em>completed</em> operations holding upon their * onset. Retrieval operations may or may not return results * reflecting in-progress writing operations. However, the retrieval * operations do always return consistent results -- either those * holding before any single modification or after it, but never a * nonsense result. For aggregate operations such as putAll and * clear, concurrent reads may reflect insertion or removal of only * some entries. In those rare contexts in which you use a hash table * to synchronize operations across threads (for example, to prevent * reads until after clears), you should either encase operations * in synchronized blocks, or instead use java.util.Hashtable. * * <p> * * This class also supports optional guaranteed * exclusive reads, simply by surrounding a call within a synchronized * block, as in <br> * <code>AbstractConcurrentReadCache t; ... Object v; <br> * synchronized(t) { v = t.get(k); } </code> <br> * * But this is not usually necessary in practice. For * example, it is generally inefficient to write: * * <pre> * AbstractConcurrentReadCache t; ... // Inefficient version * Object key; ... * Object value; ... * synchronized(t) { * if (!t.containsKey(key)) * t.put(key, value); * // other code if not previously present * } * else { * // other code if it was previously present * } * } *</pre> * Instead, just take advantage of the fact that put returns * null if the key was not previously present: * <pre> * AbstractConcurrentReadCache t; ... // Use this instead * Object key; ... * Object value; ... * Object oldValue = t.put(key, value); * if (oldValue == null) { * // other code if not previously present * } * else { * // other code if it was previously present * } *</pre> * <p> * * Iterators and Enumerations (i.e., those returned by * keySet().iterator(), entrySet().iterator(), values().iterator(), * keys(), and elements()) return elements reflecting the state of the * hash table at some point at or since the creation of the * iterator/enumeration. They will return at most one instance of * each element (via next()/nextElement()), but might or might not * reflect puts and removes that have been processed since they were * created. They do <em>not</em> throw ConcurrentModificationException. * However, these iterators are designed to be used by only one * thread at a time. Sharing an iterator across multiple threads may * lead to unpredictable results if the table is being concurrently * modified. Again, you can ensure interference-free iteration by * enclosing the iteration in a synchronized block. <p> * * This class may be used as a direct replacement for any use of * java.util.Hashtable that does not depend on readers being blocked * during updates. Like Hashtable but unlike java.util.HashMap, * this class does NOT allow <tt>null</tt> to be used as a key or * value. This class is also typically faster than ConcurrentHashMap * when there is usually only one thread updating the table, but * possibly many retrieving values from it. * <p> * * Implementation note: A slightly faster implementation of * this class will be possible once planned Java Memory Model * revisions are in place. * * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>] **/ public abstract class AbstractConcurrentReadCache extends AbstractMap implements Map, Cloneable, Serializable
类注释说明AbstractConcurrentReadCache是一个HashTable的版本,支持读多写少的应用场景,基本上缓存都是用户读多写少的场景。接下来我们再看下oscache怎么实例化的。还是从GeneralCacheAdministrator入手:
/** * Create the cache administrator. */ public GeneralCacheAdministrator() { this(null); } /** * Create the cache administrator with the specified properties */ public GeneralCacheAdministrator(Properties p) { super(p); log.info("Constructed GeneralCacheAdministrator()"); createCache(); }
它的构造函数调用了父类AbstractCacheAdministrator的构造函数:
/** * Create the AbstractCacheAdministrator. * * @param p the configuration properties for this cache. */ protected AbstractCacheAdministrator(Properties p) { loadProps(p); initCacheParameters(); if (log.isDebugEnabled()) { log.debug("Constructed AbstractCacheAdministrator()"); } }
我们看它怎么加载配置文件的:
/** * Load the properties file from the classpath. */ private void loadProps(Properties p) { config = new Config(p); }
进入Config类:
/** * Create an OSCache configuration with the specified properties. * Note that it is the responsibility of the caller to provide valid * properties as no error checking is done to ensure that required * keys are present. If you're unsure of what keys should be present, * have a look at a sample oscache.properties file. * * @param p The properties to use for this configuration. If null, * then the default properties are loaded from the <code>oscache.properties</code> * file. */ public Config(Properties p) { if (log.isDebugEnabled()) { log.debug("OSCache: Config called"); } if (p == null) { this.properties = loadProperties(PROPERTIES_FILENAME, "the default configuration"); } else { this.properties = p; } }
千呼万唤始出来,默认配置文件oscache.properties:
/** * Name of the properties file. */ private final static String PROPERTIES_FILENAME = "/oscache.properties";