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";
原文地址:https://www.cnblogs.com/wuxun1997/p/8668952.html